I have a ApplicationSettingsBase object I use to save user preferences, as well as application settings. For all of my built-in types, it properly serializes to XML and I get a nice user-editable config file. However, for my custom type "RuleCollection", this doesn't work, the only way for me to serialize it is binary which is nonsense to the user.
I have tried from the following links without success:
Add a collection of a custom class to Settings.Settings -- I originally was trying to serialize a CleanRule[] as I was able to serialize a string[] without issue. Adding the collection class as a wrapper was a band-aid that didn't work.
Custom Xml Serialization of Unknown Type and Implementing Custom XML Serialization/Deserialization of compound data type? -- I wasn't able to make settings.Save() trigger the custom XML Read/Write classes from implementing IXmlSerializable
, I think if I could force it to, this would work.
What I'm hoping for is a nice XML output where I have something like
-> Collection
-> Rule 1
-> Title
-> Description
-> Enabled
-> Mode
-> Regex
-> Args
-> Arg1
-> Arg2
-> Rule 2
-> Title
-> Description
-> Enabled
-> Mode
-> Regex
-> Args
-> Arg1
I am using .NET Framework 4.7.2
public class UserSettings : ApplicationSettingsBase
{
[UserScopedSetting]
[SettingsSerializeAs(SettingsSerializeAs.Binary)]
public RuleCollection Rules
{
get { return (RuleCollection)this["Rules"]; }
set { this["Rules"] = value; }
}
... //other properties
}
Below is the properties of the RuleCollection
and CleanRule
classes, CleanMode
is an `Enum
[Serializable]
[SettingsSerializeAs(SettingsSerializeAs.Xml)]
public class CleanRule
{
public string Title { get; private set; }
public string Description { get; private set; }
public bool Enabled { get; private set; } = true;
public CleanMode Mode { get; private set; }
public Regex R { get; private set; }
public string[] Args { get; private set; }
... //constructors and other methods
}
[Serializable]
[SettingsSerializeAs(SettingsSerializeAs.Xml)]
public class RuleCollection : IEnumerable<CleanRule>
{
public List<CleanRule> Rules { get; set; }
... // constructors and other methods
}
Finally, I am editing and saving the property like so
settings = new UserSettings();
settings.Rules = settings.Rules ?? new RuleCollection();
settings.Save();
and
RuleForm rf = new RuleForm(settings.Rules);
if(rf.ShowDialog(this) == DialogResult.OK)
{
settings.Rules = rf.Rules;
settings.Save();
}
EDIT: I've boiled this down to a more simple example EDIT 2: This example now works, it was missing a no-arg constructor as per How to serialize a class with a list of custom objects? My main code is still not working, but it would appear that serialization errors are being masked by the ApplicationSettingsBase class
public class UserSettings : ApplicationSettingsBase
{
[UserScopedSetting]
public Test Test
{
get { return (Test)this["Test"]; }
set { this["Test"] = value; }
}
}
[Serializable]
public class Test
{
public int I { get; set; }
public string S { get; set; }
public Test(){ }
public Test(int i, string s)
{
I = i;
S = s;
}
}
settings = new UserSettings();
settings.Test = new Test(30, "Tom");
settings.Save();
Result:
<setting name="Test" serializeAs="Xml">
<value>
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<I>30</I>
<S>Tom</S>
</Test>
</value>
</setting>
CodePudding user response:
TL:DR; there were a bunch of little things wrong that SHOULD HAVE been raised as exceptions, however, due to using the ApplicationSettingsBase class, serialization errors were being suppressed.
I found a post that gave me explicit "write to" and "read from" xml file for a generic type <T>
and used that to force the object I was having trouble with to xml and raise those errors.
public static void WriteToXmlFile<T>(string filePath, T objectTowrite, bool append = false) where T : new()
{
TextWriter writer = null;
try
{
var serializer = new XmlSerializer(typeof(T));
writer = new StreamWriter(filePath, append);
serializer.Serialize(writer, objectTowrite);
}
finally
{
if (writer != null)
writer.Close();
}
}
public static T ReadFromXmlFile<T>(string filePath) where T : new()
{
TextReader reader = null;
try
{
var serializer = new XmlSerializer(typeof(T));
reader = new StreamReader(filePath);
return (T)serializer.Deserialize(reader);
}
finally
{
if (reader != null)
reader.Close();
}
}
The first error had to do with my collection function inheriting from IEnumerable but not implementing Add()
public class RuleCollection : IEnumerable<CleanRule>
{
[XmlArray]
public List<CleanRule> Rules { get; set; }
public RuleCollection(){ ... }
public RuleCollection(IEnumerable<CleanRule> rules){ ... }
public void Add(CleanRule rule){ ... }
public void Remove(CleanRule rule){ ... }
public void Enable(CleanRule rule){ ... }
public void Disable(CleanRule rule){ ... }
}
The second error was that my classes attributes had private setters. I'm not crazy about those being public but I guess that's just what has to happen.
public class CleanRule
{
public string Title { get; set; }
public string Description { get; set; }
...
}
And I'm still working on the third error, which is serialization of a Regex object.