Search code examples
c#xmlserializationapplicationsettingsbase

UserSettings can't serialize custom type as XML, only binary


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>

Solution

  • 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.