0

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>
Bukowskaii
  • 31
  • 1
  • 8
  • You should be able to serialize any class provided the root class is not an array. When you have an array at the root you must add a singleton root class. – jdweng Aug 10 '22 at 21:36
  • @jdweng I know I SHOULD, that's exactly the problem. Even if I boil this down to something simple like a custom class with only an int and a string as properties, I cannot serialize to XML so I feel like I'm missing something simple. – Bukowskaii Aug 10 '22 at 21:38
  • Are all properties public? Do you get an exception? What does output look like? Do you have write permission to folder? Do you have any inherited classes? The you must define XmlInclude. – jdweng Aug 10 '22 at 21:48
  • @jweng Yes, properties are public. No exceptions or errors. Edited post with output, basically an empty xml tag. Yes, the file is being written OK for all other properties, and even this one when `serializeAz="Binary"`. `RuleCollection` inherits from `IEnumerable` but `CleanRule` does not, and the simplified test case does not with same result. How do I define XmlInclude? I've tried inheriting from `IXmlSerializable` with no luck, the ApplicationSettingsBase.Save() didn't seem to call the custom methods. – Bukowskaii Aug 10 '22 at 21:59
  • XmlInclude add a type attribute which is the inherited class. The tag name is the base class name. Your xml does not have a type attribute because you are missing the XmlInclude : https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlincludeattribute?view=net-6.0 Try adding sample data to your classes and serialize. Then open xml file to see what the results look like. – jdweng Aug 11 '22 at 01:23

1 Answers1

0

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.

Bukowskaii
  • 31
  • 1
  • 8