Avoid duplicated code in XML serializable classes - c#

I have two Serializable classes with very similar code. Actually, except for the part where specific constructor is called, serialization code is identical.
Is there a way to create a common class to contain the common parts, so that specific classes (subclasses?) can implement only the constructor part? I can think of generics, factory pattern, but could not figure out how to do it.
// Fictitious classes
[Serializable]
public class FlightParameters {
public double MaxHeight { get; set; }
pulbic double MaxSpeedKmPerHour { get; set; }
public static FlightParameters Load(String fname) {
FlightParameters result;
using (var fs = new FileStream(fname, FileMode.OpenOrCreate)) {
var serializer = new XmlSerializer(typeof(FlightParameters));
try {
result = (FlightParameters)serializer.Deserialize(fs);
}
// catch "file not found"
catch (InvalidOperationException) {
result = new FlightParameters() {
MaxHeight = 30000;
MaxSpeedKmPerHour = 1500;
}
serializer.Serialize(fs, result);
}
return result;
}
}
}
[Serializable]
public class SailingParameters {
public double MaxDepth { get; set; }
pulbic double MaxSpeedKnots { get; set; }
public static SailingParameters Load(String fname) {
SailingParameters result;
using (var fs = new FileStream(fname, FileMode.OpenOrCreate)) {
var serializer = new XmlSerializer(typeof(SailingParameters));
try {
result = (SailingParameters)serializer.Deserialize(fs);
}
// catch "file not found"
catch (InvalidOperationException) {
result = new SailingParameters() {
MaxDepth = 13000;
MaxSpeedKnots = 15;
}
serializer.Serialize(fs, result);
}
return result;
}
}
}
Usage:
FlightParameters _fparam = FlightParameters.Load(somePath);
SailingParameters _sparam = SailingParameters.Load(someOtherPath);

The easiest way I can see to do that would be something like:
static class XmlUtils {
public static T Load<T>(string filename, Func<T> onMissing = null)
where T : class, new()
{
using (var fs = File.OpenRead(filename)) {
var serializer = new XmlSerializer(typeof(T));
try {
return (T)serializer.Deserialize(fs);
} catch (InvalidOperationException) { // catch "file not found"
return onMissing == null ? new T() : onMissing();
}
}
}
}
allowing something like;
public static SailingParameters Load(string filename) {
return XmlUtils.Load<SailingParameters>(filename, () => new SailingParameters {
MaxDepth = 13000;
MaxSpeedKnots = 15;
});
}

Related

C# Can i implement interface with IEnumerable object and struct?

So I want to write an interface, which should be able to be implemented with any data. This is interface i wrote till now. The reason I chose IEnumerable is because I need to give class Computer or struct Processor
public interface IData<T> where T : IEnumerable<object>
{
public T ReadData();
public void WriteData(T list);
}
And I have two different datas, one is Computer, which is a class. And the other one is Processor (struct)
public struct Processor
{
public string Name { get; set; }
public string AmazonLink { get; set; }
public string AmazonBin { get; set; }
public Processor(string name, string link)
{
Name = name;
try
{
//constructor parses elements which is needed to generate AmazonURL in URLGenerator project
AmazonLink = link.Substring(0, link.IndexOf("&dc"));
string binStart = link.Substring(link.IndexOf("bin%") + 4);
AmazonBin = "%7C" + binStart.Substring(2);
}
catch (Exception e)
{
throw new InnerCustomException("Erorr occured while trying to substring the link", e);
}
}
I tried to do that like this, but it seems like I am not allowed to do that because of boxing?
public class ProcessorServiceCSV : IData<IEnumerable<Processor>>
{ private string Path { get; set; }
private FileMode Filemode { get; set; }
public ProcessorServiceCSV(string path, FileMode fileMode)
{
Path = path;
Filemode = fileMode;
}
//reads Processor list from CSV file
public IEnumerable<Processor> ReadData()
{
try
{
using (var reader = new StreamReader(Path))
using (var csv = new CsvReader(reader))
{
csv.Configuration.CultureInfo = CultureInfo.InvariantCulture;
csv.Configuration.Delimiter = ",";
csv.Configuration.RegisterClassMap<ProcessorMap>();
var records = csv.GetRecords<Processor>().ToList();
return records.ToList();
}
}
catch (FileNotFoundException)
{
throw new DataCustomException("File not found", this);
}
catch (Exception e)
{
throw new DataCustomException("Something's wrong happened:" + e.Message, this);
}
} public void WriteData(IEnumerable<Processor> processors)
{
try
{
using (var stream = File.Open(Path, Filemode))
using (StreamWriter sw = new StreamWriter(stream))
using (CsvWriter cw = new CsvWriter(sw))
{
foreach (Processor processor in processors)
{
cw.Configuration.RegisterClassMap<ProcessorMap>();
cw.WriteRecord<Processor>(processor);
cw.NextRecord();
}
}
}
catch (FileNotFoundException)
{
throw new DataCustomException("File not found", this);
}
catch (FileLoadException)
{
throw new DataCustomException("File could not be opened", this);
}
catch (Exception e)
{
throw new DataCustomException("Something's wrong happened:" + e.Message, this);
}
}
}
}
I know I could change Processor from struct to class, but is it possible to keep struct? Thank you in advance
You have a lot of other problems, including not giving us a complete, working bit of code.
However, it looks like you should be able to do what you want to do if you use an Interface for the Processor struct instead of the actual struct type.
Also, notice how I changed the type for T in your classes. You don't need IEnumerable in your T constraint. I did delete some of your code to get it to somewhat work (the exception in the struct constructor, e.g.), so you will need to do some more work here.
public interface IData<T>
{
IEnumerable<T> ReadData();
void WriteData(IEnumerable<T> list);
}
public interface IProcessor {
string Name { get; set; }
string AmazonLink { get; set; }
string AmazonBin { get; set; }
}
public struct Processor : IProcessor
{
public string Name { get; set; }
public string AmazonLink { get; set; }
public string AmazonBin { get; set; }
public Processor(string name, string link)
{
Name = name;
//constructor parses elements which is needed to generate AmazonURL in URLGenerator project
AmazonLink = link.Substring(0, link.IndexOf("&dc"));
string binStart = link.Substring(link.IndexOf("bin%") + 4);
AmazonBin = "%7C" + binStart.Substring(2);
}
}
public class ProcessorServiceCSV<T> : IData<T> where T: IProcessor
{ private string Path { get; set; }
private FileMode Filemode { get; set; }
public ProcessorServiceCSV(string path, FileMode fileMode)
{
Path = path;
Filemode = fileMode;
}
//reads Processor list from CSV file
public IEnumerable<T> ReadData()
{
try
{
using (var reader = new StreamReader(Path))
using (var csv = new CsvReader(reader))
{
csv.Configuration.CultureInfo = CultureInfo.InvariantCulture;
csv.Configuration.Delimiter = ",";
csv.Configuration.RegisterClassMap<ProcessorMap>();
var records = csv.GetRecords<Processor>().ToList();
return records.ToList();
}
}
catch (FileNotFoundException)
{
throw new DataCustomException("File not found", this);
}
catch (Exception e)
{
throw new DataCustomException("Something's wrong happened:" + e.Message, this);
}
}
}
Is this the basic skeleton code of what you are trying to do? Note that the generic collection is IEnumerable<T> and not IEnumetable<object> and hence I updated your IData<T> definition
public class Computer
{
}
public struct Processor
{
}
public interface IData<T>
{
IEnumerable<T> ReadData();
void WriteData(IEnumerable<T> list);
}
public class ComputerData : IData<Computer>
{
public IEnumerable<Computer> ReadData()
{
throw new NotImplementedException();
}
public void WriteData(IEnumerable<Computer> list)
{
throw new NotImplementedException();
}
}
public class ProcessorData : IData<Processor>
{
public IEnumerable<Processor> ReadData()
{
throw new NotImplementedException();
}
public void WriteData(IEnumerable<Processor> list)
{
throw new NotImplementedException();
}
}
Please indicate if this code meets your requirements, and if not why.

Deserialize xml into a list

I have a normal list based in model like:
Model:
public class ProjectHistoryModel
{
public int JobNumber { get; set; }
public int DesignKey { get; set; }
public string ProjectName { get; set; }
}
In class I have a list of this model like:
public List<ProjectHistoryModel> ProjectHistoryModel = new List<ProjectHistoryModel>();
Then I save that list into xml file as:
Serialize list:
public static string SerializeObject<T>(this T value)
{
if (value == null)
{
return string.Empty;
}
try
{
var xmlserializer = new XmlSerializer(typeof(T));
var stringWriter = new StringWriter();
using (var writer = XmlWriter.Create(stringWriter))
{
xmlserializer.Serialize(writer, value);
return stringWriter.ToString();
}
}
catch (Exception ex)
{
throw new Exception("An error occurred", ex);
}
}
So I save list just sending it to that method as:
var historyXml = ProjectHistoryModel.SerializeObject();
XML.HistoryProjects = historyXml;
XML.SaveXML();
Now my question is: How can I deserialize this xml and convert to a list again?
I try it something like this but I get stuck:
public static List<string> Load()
{
var xmlList = XML.HistoryProjects;
using (var stream = System.IO.File.OpenRead(FileName))
{
var serializer = new XmlSerializer(xmlList));
return serializer.Deserialize(stream) as [xmlList];
}
}
Regards
You just need to do the same thing in reverse, using a StringReader instead of a writer.
public static T DeserializeObject<T>(this string source)
{
if (string.IsNullOrEmpty(source))
{
return default(T);
}
try
{
var xmlserializer = new XmlSerializer(typeof(T));
var stringReader = new StringReader(source);
using (var reader = XmlReader.Create(stringReader))
{
var result = xmlserializer.Deserialize(reader);
return (T)result;
}
}
catch (Exception ex)
{
throw new Exception("An error occurred", ex);
}
}
Then call it with:
var input = new List<ProjectHistoryModel>();
var serialized = input.SerializeObject();
var output = serialized.DeserializeObject<List<ProjectHistoryModel>>();
Here is a link to a working example on DotNetFiddle.

Write/Restore ObservableCollection<T>

I have huge problem with saveing and restore ObservableCollection to IsolatedData.
I'm trying with this code.
Helper class for Observable
public class ListItem {
public String Title { get; set; }
public bool Checked { get; set; }
public ListItem(String title, bool isChecked=false) {
Title = title;
Checked = isChecked;
}
private ListItem() { }
}
IsoHelper
public class IsoStoreHelper {
private static IsolatedStorageFile _isoStore;
public static IsolatedStorageFile IsoStore {
get { return _isoStore ?? (_isoStore = IsolatedStorageFile.GetUserStoreForApplication()); }
}
public static void SaveList<T>(string folderName, string dataName, ObservableCollection<T> dataList) where T : class {
if (!IsoStore.DirectoryExists(folderName)) {
IsoStore.CreateDirectory(folderName);
}
if (IsoStore.FileExists(folderName + "\\" + dataName+".dat")) {
IsoStore.DeleteFile(folderName + "\\" + dataName + ".dat");
}
string fileStreamName = string.Format("{0}\\{1}.dat", folderName, dataName);
try {
using (var stream = new IsolatedStorageFileStream(fileStreamName, FileMode.Create, IsoStore)) {
var dcs = new DataContractSerializer(typeof(ObservableCollection<T>));
dcs.WriteObject(stream, dataList);
}
} catch (Exception e) {
Debug.WriteLine(e.Message);
}
}
public static ObservableCollection<T> LoadList<T>(string folderName, string dataName) where T : class {
var retval = new ObservableCollection<T>();
if (!IsoStore.DirectoryExists(folderName) || !IsoStore.FileExists(folderName + "\\" + dataName + ".dat")) {
return retval;
}
string fileStreamName = string.Format("{0}\\{1}.dat", folderName, dataName);
var isf = IsoStore;
try {
var fileStream = IsoStore.OpenFile(fileStreamName, FileMode.OpenOrCreate);
if (fileStream.Length > 0) {
var dcs = new DataContractSerializer(typeof(ObservableCollection<T>));
retval = dcs.ReadObject(fileStream) as ObservableCollection<T>;
}
} catch {
retval = new ObservableCollection<T>();
}
return retval;
}
}
And I'm trying to use it this way
public partial class MainPage : PhoneApplicationPage{
public ObservableCollection<ListItem> ListItems = new ObservableCollection<ListItem>();
bool isListSaved;
private void Panorama_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) {
if (strTag.Equals("list") ) {
isListSave = false;
ListItems = IsoStoreHelper.LoadList<ListItem>("settings", "ListItems");
} else if (!isListSave) {
IsoStoreHelper.SaveList<ListItem>("settings", "ListItems", ListItems);
}
}
}
I keep getting A first chance exception of type 'System.Security.SecurityException' occurred in System.Runtime.Serialization.ni.dll when I try read saved file at line ReadObject(fileStream) but the FileAccess looks fine.
Any conclusion will be appreciated.
SOLVED:
Like Dmytro Tsiniavskyi said I totaly forgot about [DataContract] and [DataMember] in ListItem. Whats more I found better solution for saving and loading data. I end up with this code for ListItem
[DataContract]
public class ListItem {
[DataMember]
public String Title { get; set; }
[DataMember]
public bool Checked { get; set; }
public ListItem(String title, bool isChecked=false) {
Title = title;
Checked = isChecked;
}
private ListItem() { }
}
And this code for save/load collection which was originally founded here and modified a litte bit for better useage.
public partial class IsolatedRW {
public static void SaveData<T>(string fileName, T dataToSave) {
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) {
try {
if (store.FileExists(fileName)) {
store.DeleteFile(fileName);
}
if (!store.DirectoryExists("Settings")) store.CreateDirectory("Settings");
IsolatedStorageFileStream stream;
using (stream = store.OpenFile("Settings/"+fileName+".xml", System.IO.FileMode.Create, System.IO.FileAccess.Write)) {
var serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(stream, dataToSave);
}
stream.Close();
} catch (System.Security.SecurityException e) {
//MessageBox.Show(e.Message);
return;
}
Debug.WriteLine(store.FileExists("Settings/" + fileName + ".xml"));
}
}
public static T ReadData<T>(string fileName) {
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) {
Debug.WriteLine(store.FileExists("Settings/" + fileName + ".xml"));
if (store.FileExists("Settings/" + fileName + ".xml")) {
IsolatedStorageFileStream stream;
using (stream = store.OpenFile("Settings/"+fileName+".xml", FileMode.OpenOrCreate, FileAccess.Read)) {
try {
var serializer = new DataContractSerializer(typeof(T));
return (T)serializer.ReadObject(stream);
} catch (Exception) {
return default(T);
}
}
stream.Close();
}
return default(T);
}
}
}
Try to add [DataContract] attribute for your ListItem class.
[DataContract]
public class ListItem {
[DataMember]
public String Title { get; set; }
[DataMember]
public bool Checked { get; set; }
public ListItem(String title, bool isChecked=false) {
Title = title;
Checked = isChecked;
}
private ListItem() { }
}

Remove namespace from DataContract doesn't work

I have to two simple serialize/desirialize methods,
Mapping:
[System.Runtime.Serialization.DataContract(Namespace = "", Name = "PARAMS")]
public sealed class CourseListRequest {
[DataMember(Name = "STUDENTID")]
public int StudentId { get; set; }
[DataMember(Name = "YEAR")]
public string Year { get; set; }
[DataMember(Name = "REQUESTTYPE")]
public int RequestType { get; set; }
}
public static string Serialize<T>(this T value) {
if (value == null) throw new ArgumentNullException("value");
try {
var dcs = new DataContractSerializer(typeof (T));
string xml;
using (var ms = new MemoryStream()) {
dcs.WriteObject(ms, value);
xml = Encoding.UTF8.GetString(ms.ToArray());
}
return xml;
}
catch (Exception e) {
throw;
}
}
public static T Deserialize<T>(this string xml) where T : class {
if (string.IsNullOrEmpty(xml)) {
return default(T);
}
try {
var dcs = new DataContractSerializer(typeof (T));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml))) {
ms.Position = 0;
return dcs.ReadObject(ms) as T;
}
}
catch (Exception e) {
throw;
}
}
result:
<PARAMS xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><REQUESTTYPE>36</REQUESTTYPE><STUDENTID>0</STUDENTID><YEAR>תשע</YEAR></PARAMS>
How to remove xmlns:i="http://www.w3.org/2001/XMLSchema-instance" ?? On serializing
Switch to using XmlSerializer
System.Xml.Serialization.XmlSerializer
This will generate plain XML with no namespaces

using generics with xml deserialisation

I want to pass an object into the resulting type of an xml deserialisation and maintain strong typing.
So the deserialisation class can take any type that implements the IResult interface which in this case is Result and Result2.
I have got this working by making the getObject method return dynamic but i would much rather keep the compile time checking and i think it should be possible.
I have tried using generics, as in the example below, but the deser.getObject(doc()); line gives me a "cannot be inferred from usage" compile error.
Thanks for all help.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace SOQuestion
{
class Program
{
static void Main(string[] args)
{
var deser = new Deserialised(new Result());
var result = deser.getObject(doc());
var deser2 = new Deserialised(new Result2());
var result2 = deser.getObject(doc());
Console.Writeline(result.status);
Console.Writeline(result2.status);
}
public XmlDocument doc()
{
var doc = new XmlDocument();
var el = (XmlElement)doc.AppendChild(doc.CreateElement("Result"));
el.SetAttribute("status", "ok");
el.SetAttribute("status2", "not ok");
return doc;
}
}
class Deserialised
{
private IResult result;
private Type resultType;
public Deserialised(IResult _result)
{
result = _result;
resultType = Type.GetType(result.GetType().AssemblyQualifiedName);
}
public T getObject<T>(XmlDocument xml)
{
var mySerializer = new XmlSerializer(resultType);
var myStream = new MemoryStream();
xml.Save(myStream);
myStream.Position = 0;
var r = mySerializer.Deserialize(myStream);
return (T)r;
}
}
interface IResult
{
public string status {get;set;}
}
[Serializable]
public class Result :IResult
{
[XmlAttribute]
public string status { get; set; }
}
[Serializable]
public class Result2 : IResult
{
[XmlAttribute]
public string status2 { get; set; }
}
}
Indeed, that isn't going to work - the compiler has no way of knowing the T from that. Remember that the T comes from the caller at compile-time, not from the result of the method at runtime. There are ways to switch between reflection/generics, but it is ugly and isn't going to help much here. I would just return object instead:
public object GetObject(XmlDocument xml) {
var mySerializer = new XmlSerializer(resultType);
using(var myStream = new MemoryStream()) {
xml.Save(myStream);
myStream.Position = 0;
return mySerializer.Deserialize(myStream);
}
}
and then let the caller handle the dynamic etc:
var deser = new Deserialised(new Result());
dynamic result = deser.GetObject(doc());
var deser2 = new Deserialised(new Result2());
dynamic result2 = deser.GetObject(doc());
Console.Writeline(result.status);
Console.Writeline(result2.status);
Because of the dynamic above, the .status in the two Console.WriteLine will still work.
In my opinion the answer you provided yourself overcomplicates things. (In addition to doing some other weird stuff, ... see my comment.)
There is nothing gained in using generics here. You can just as well use the following approach, and both of your requirements will still hold.
public IResult GetObject(XmlDocument xml)
{
var mySerializer = new XmlSerializer(resultType);
using (var myStream = new MemoryStream())
{
xml.Save(myStream);
myStream.Position = 0;
return (IResult)mySerializer.Deserialize(myStream);
}
}
... simply call it as follows:
var deser = new Deserialised(new Result());
var result = (Result)deser.getObject(doc());
var deser2 = new Deserialised(new Result2());
var result2 = (Result2)deser.getObject(doc());
Casting to anything which doesn't implement IResult will trigger a compiler error.
It is not really clear what you are trying to do here, but suppose you made Deserialised generic.
class Deserialised<T>
where T : IResult
{
private T result;
private Type resultType;
public Deserialised(T _result)
{
result = _result;
}
public T getObject(XmlDocument xml)
{
var mySerializer = new XmlSerializer(typeof(T));
var myStream = new MemoryStream();
xml.Save(myStream);
myStream.Position = 0;
var r = (T)mySerializer.Deserialize(myStream);
return r;
}
}
Why are you even passing _result as a parameter and storing it? My guess is you only needed it since you didn't know about typeof()? In that case simply drop it. After doing so again you just end up with a class which defines your generic parameter, with again the sole purpose to define the required cast.
So i have found a solution that my two requirements of 1. that passed in type implements IResult interface 2. that the returned object is statically typed.
A generic method with a constraint like below
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace SOQuestion
{
class Program
{
static void Main(string[] args)
{
var result = new Deserialised().getObject<Result>();
var result2 = new Deserialised().getObject<Result2>();
Console.WriteLine(result.status);
Console.WriteLine(result.errorMessage);
Console.ReadLine();
}
}
class Deserialised
{
public T getObject<T>() where T : IResult
{
try
{
var instance = Activator.CreateInstance<T>();
var mySerializer = new XmlSerializer(instance.GetType());
var myStream = new MemoryStream();
doc().Save(myStream);
myStream.Position = 0;
var r = mySerializer.Deserialize(myStream);
throw new DivideByZeroException();
return (T)r;
}
catch (Exception exp)
{
var instance = Activator.CreateInstance<T>();
instance.errorMessage = "something wrong here";
return instance;
}
;
}
public static XmlDocument doc()
{
var doc = new XmlDocument();
var el = (XmlElement)doc.AppendChild(doc.CreateElement("Result"));
el.SetAttribute("status", "ok");
el.SetAttribute("status2", "notok");
return doc;
}
}
interface IResult
{
string status { get; set; }
string errorMessage { get; set; }
}
[Serializable]
public class Result : IResult
{
[XmlAttribute]
public string status { get; set; }
[XmlAttribute]
public string errorMessage { get; set; }
[XmlAttribute]
public string message { get; set; }
}
[Serializable]
public class Result2 : IResult
{
[XmlAttribute]
public string status { get; set; }
[XmlAttribute]
public string message2 { get; set; }
[XmlAttribute]
public string errorMessage { get; set; }
}
[Serializable]
public class Result3
{
[XmlAttribute]
public string status { get; set; }
[XmlAttribute]
public string message2 { get; set; }
[XmlAttribute]
public string errorMessage { get; set; }
}
}

Categories

Resources