-1

I am trying to write functions that save a type, the Dispatcher type shown below, into a file, and then reload it later. I wrote that following functions, but they are not working. I get an exception:

Exception thrown: 'System.InvalidOperationException' in System.Xml.dll

Elsewhere I read that maybe because the member in the class is private then I should use BinaryFormatter, but it did not work.

What am I doing wrong ?

(The Dispatcher class will be used to store messages and also will allow users to pull messages from it. so I want to backup the data in case of an error and then be able to reload it).

public class Dispatcher
{
    private Dictionary<int, Dictionary<int, Dictionary<int, Message>>> m_DataBase;

    private Dispatcher()
    {
        m_DataBase = new Dictionary<int, Dictionary<int, Dictionary<int, Message>>>();
    }

    public static Dispatcher LoadFromFile()
    {
        Dispatcher loadedDataBase;

        try
        {
            using (Stream stream = new FileStream(@".\DispatcherDataBase.xml", FileMode.Open))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Dispatcher));
                loadedDataBase = serializer.Deserialize(stream) as Dispatcher;
            }
        }
        catch (Exception)
        {
            loadedDataBase = new Dispatcher();
        }

        return loadedDataBase;
    }

    public void SaveToFile()
    {
        FileMode wantedFileModeForStream;

        try
        {
            if (File.Exists(@".\DispatcherDataBase.xml"))
            {
                wantedFileModeForStream = FileMode.Truncate;
            }
            else
            {
                wantedFileModeForStream = FileMode.CreateNew;
            }

            using (Stream stream = new FileStream(@".\DispatcherDataBase.xml", wantedFileModeForStream))
            {
                XmlSerializer serializer = new XmlSerializer(this.GetType());
                serializer.Serialize(stream, this);
            }
        }
        catch (Exception)
        {
        }
    }
}
dbc
  • 104,963
  • 20
  • 228
  • 340
chinakid
  • 3
  • 3
  • Are your getting an error (just that it isn't working isn't good enough)? Does the SaveToFile output a valid xml file? – Kevin Nov 22 '19 at 04:00
  • I get an Exception. – chinakid Nov 22 '19 at 04:06
  • this is the Exception I get :" Exception thrown: 'System.InvalidOperationException' in System.Xml.dll " I get this Exception also if the member is public – chinakid Nov 22 '19 at 04:13
  • I think this class isn't xml serializable. https://learn.microsoft.com/en-us/dotnet/standard/serialization/introducing-xml-serialization – Nikki9696 Nov 22 '19 at 04:24
  • 2
    Notably: A class must have a parameterless constructor to be serialized by XmlSerializer. Only public properties and fields can be serialized. Properties must have public accessors (get and set methods). https://stackoverflow.com/questions/2911514/why-doesnt-xmlserializer-support-dictionary – Nikki9696 Nov 22 '19 at 04:25
  • Is there any other way to serialize this class ?(when the member is private and its a Dictionary), I didnt find any way to do so. – chinakid Nov 22 '19 at 04:54
  • You should be able to use `DataContractSerializer`. However your question doesn't include a definition for `Message` so I can't say for sure. Could you please [edit] your question to include a [mcve]? – dbc Nov 22 '19 at 06:03
  • @chinakid - I think I was able to figure out your problem, but in the future, when you ask a question, please 1) Include a [mcve] (`Message` was not included). 2) Include the full `ToString()` output of any exceptions including the exception type, message, traceback and inner exceptions. Often the inner exceptions from `XmlSerializer` will be explanatory. – dbc Nov 22 '19 at 06:53

1 Answers1

1

You are trying to use XmlSerializer to serialize your Dispatcher type and are encountering three separate problems:

  1. XmlSerializer requires your type to have a public parameterless constructor.

  2. XmlSerializer does not support dictionaries.

  3. XmlSerializer will not serialize non-public members such as m_DataBase.

DataContractSerializer (as well as DataContractJsonSerializer) do not have these limitations. If you mark your Dispatcher type with data contract attributes you will be able to serialize it to XML using this serializer.

Thus if you modify your type as follows:

public static class Constants
{
    public const string DataContractNamespace = ""; // Or whatever
}

[DataContract(Name = "Dispatcher", Namespace = Constants.DataContractNamespace)]
public partial class Dispatcher
{
    [DataMember]
    private Dictionary<int, Dictionary<int, Dictionary<int, Message>>> m_DataBase;

    private Dispatcher()
    {
        m_DataBase = new Dictionary<int, Dictionary<int, Dictionary<int, Message>>>();
    }

    [System.Runtime.Serialization.OnDeserialized]
    void OnDeserializedMethod(System.Runtime.Serialization.StreamingContext context)
    {
        // Ensure m_DataBase is not null after deserialization (DataContractSerializer does not call the constructor).
        if (m_DataBase == null)
            m_DataBase = new Dictionary<int, Dictionary<int, Dictionary<int, Message>>>();
    }

    internal const string FileName = @"DispatcherDataBase.xml";

    public static Dispatcher LoadFromFile()
    {
        Dispatcher loadedDataBase;

        try
        {
            using (var stream = new FileStream(FileName, FileMode.Open))
            {
                var serializer = new DataContractSerializer(typeof(Dispatcher));
                loadedDataBase = serializer.ReadObject(stream) as Dispatcher;
            }
        }
        catch (Exception)
        {
            loadedDataBase = new Dispatcher();
        }

        return loadedDataBase;
    }

    public void SaveToFile()
    {
        using (var stream = new FileStream(FileName, FileMode.Create))
        using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) // Optional indentation for readability only.
        {
            var serializer = new DataContractSerializer(this.GetType());
            serializer.WriteObject(writer, this);
        }
    }
}

// Not shown in question, added as an example
[DataContract(Name = "Message", Namespace = Constants.DataContractNamespace)]
public class Message
{
    [DataMember]
    public string Value { get; set; }
}

You will be able to round-trip your Dispatcher class to XML. Demo fiddle #1 here.

Alternatively, you could use DataContractJsonSerializer and serialize to JSON by simply swapping serializers and eliminating the optional XmlWriter:

    public static Dispatcher LoadFromFile()
    {
        Dispatcher loadedDataBase;

        try
        {
            using (var stream = new FileStream(FileName, FileMode.Open))
            {
                var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(Dispatcher));
                loadedDataBase = serializer.ReadObject(stream) as Dispatcher;
            }
        }
        catch (Exception)
        {
            loadedDataBase = new Dispatcher();
        }

        return loadedDataBase;
    }

    public void SaveToFile()
    {
        using (var stream = new FileStream(FileName, FileMode.Create))
        {
            var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(this.GetType());
            serializer.WriteObject(stream, this);
        }
    }

Demo fiddle #2 here, which results in a fairly simple, clean-looking serialization format:

{"m_DataBase":[{"Key":1,"Value":[{"Key":2,"Value":[{"Key":3,"Value":{"Value":"hello"}}]}]}]}

could also be used to serialize this type if you are willing to use a 3rd party component.

dbc
  • 104,963
  • 20
  • 228
  • 340