Search code examples
c#xmlserializer

Selective serialization of XML properties


This is a work in progress. I have these C# classes that I am developing to serialize XML data:

using SimpleLogger;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Xml.Serialization;

namespace MSAToolsLibrary.SRRAssignmentHistory
{
    [Guid("xxx")]
    [ComVisible(true)]
    public enum AssignmentMode
    {
        Weekly,
        Midweek,
        Weekend
    }

    [Guid("xxx")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [ComVisible(true)]
    public interface IAssignmentHistorySRRInterface
    {
        void SetPathXML(String strPathXML, out Int64 iResult);
        bool SaveDutyAssignmentHistory();
        bool ReadDutyAssignmentHistory(out Int64 iNumberEntriesRead);
        void Test();
    }

    public class AssignmentHistorySRR : IAssignmentHistorySRRInterface
    {
        private DutyAssignmentHistory _DutyAssignmentHistory;
        private string _strPathXML;

        public AssignmentHistorySRR()
        {
            _DutyAssignmentHistory = new DutyAssignmentHistory();
            _strPathXML = "";
        }

        public void SetPathXML(string strPathXML, out Int64 iResult)
        {
            _strPathXML = strPathXML;

            iResult = 1;
        }

        public bool ReadDutyAssignmentHistory(out Int64 iNumberEntriesRead)
        {
            bool bRead = false;
            iNumberEntriesRead = 0;

            try
            {
                _DutyAssignmentHistory.DutyAssignments.Clear(); // Reset

                XmlSerializer x = new XmlSerializer(_DutyAssignmentHistory.GetType());
                using (StreamReader reader = new StreamReader(_strPathXML))
                {
                    _DutyAssignmentHistory = (DutyAssignmentHistory)x.Deserialize(reader);
                    _DutyAssignmentHistory.BuildDutyAssignmentDictionaryFromList();

                    iNumberEntriesRead = _DutyAssignmentHistory.DutyAssignments.Count;
                    bRead = true;
                }
            }
            catch (Exception ex)
            {
                SimpleLog.Log(ex);
            }

            return bRead;
        }

        public bool SaveDutyAssignmentHistory()
        {
            bool bSaved = false;

            try
            {
                _DutyAssignmentHistory.BuildDutyAssignmentListFromDictionary();
                XmlSerializer x = new XmlSerializer(_DutyAssignmentHistory.GetType());
                using (StreamWriter writer = new StreamWriter(_strPathXML))
                {
                    x.Serialize(writer, _DutyAssignmentHistory);

                    bSaved = true;
                }
            }
            catch (Exception ex)
            {
                SimpleLog.Log(ex);
            }

            return bSaved;
        }

        public void Test()
        {
            SetPathXML(@"d:\testsrr.xml", out long iResult);

            List<int> columnindex = new List<int>();
            List<string> names = new List<string>();

            columnindex.Add(1);
            columnindex.Add(2);
            columnindex.Add(3);
            columnindex.Add(4);
            columnindex.Add(5);
            columnindex.Add(6);

            names.Add("Assign 1");
            names.Add("Assign 2");
            names.Add("Assign 3");
            names.Add("Assign 4");
            names.Add("Assign 5");
            names.Add("Assign 6");

            _DutyAssignmentHistory.AddAssignments("W20180101", -1, AssignmentMode.Weekly, columnindex, names);

            SaveDutyAssignmentHistory();
        }

    }

    [XmlRoot(ElementName = "DutyAssignmentHistory", Namespace = "http://www.publictalksoftware.co.uk/msa")]
    public class DutyAssignmentHistory
    {
        public List<DutyAssignmentEntry> DutyAssignments
        {
            get => _DutyAssignments; set => _DutyAssignments = value;
        }
        private List<DutyAssignmentEntry> _DutyAssignments;
        private Dictionary<string, DutyAssignmentEntry> _DutyAssignmentsDictionary = new Dictionary<string, DutyAssignmentEntry>();

        public DutyAssignmentHistory()
        {
            _DutyAssignments = new List<DutyAssignmentEntry>();
        }

        public void BuildDutyAssignmentDictionaryFromList()
        {
            _DutyAssignmentsDictionary = _DutyAssignments.ToDictionary(x => x.Week, x => x);
        }

        public void BuildDutyAssignmentListFromDictionary()
        {
            _DutyAssignments = _DutyAssignmentsDictionary.Select(x => x.Value).ToList();
        }

        public void AddAssignments(string Week, int Template, AssignmentMode eMode, List<int> ColumnIndex, List<string> Names)
        {
            DutyAssignmentEntry oEntry = new DutyAssignmentEntry
            {
                Week = Week,
                Template = Template,
                WeeklyMode = eMode == AssignmentMode.Weekly
            };

            int iNumNames = Names.Count;
            for(int iName = 0; iName < iNumNames; iName++)
            {
                Assignment oAssign = new Assignment
                {
                    ColumnIndex = ColumnIndex[iName],
                    Name = Names[iName]
                };

                if (eMode == AssignmentMode.Midweek)
                    oEntry.MidweekAssignments.Add(oAssign);
                else if (eMode == AssignmentMode.Weekend)
                    oEntry.WeekendAssignments.Add(oAssign);
                else
                    oEntry.WeeklyAssignments.Add(oAssign);
            }

            _DutyAssignmentsDictionary.Add(Week, oEntry);
        }

    }

    public class DutyAssignmentEntry
    {
        [XmlAttribute]
        public string Week
        {
            get => _Week; set => _Week = value;
        }
        private string _Week;

        [XmlAttribute]
        public int Template
        {
            get => _Template; set => _Template = value;
        }
        private int _Template;

        [XmlAttribute]
        public bool WeeklyMode
        {
            get => _WeeklyMode; set => _WeeklyMode = value;
        }
        private bool _WeeklyMode;

        public List<Assignment> MidweekAssignments
        {
            get => _MidweekAssignments; set => _MidweekAssignments = value;
        }
        private List<Assignment> _MidweekAssignments;

        public List<Assignment> WeekendAssignments
        {
            get => _WeekendAssignments; set => _WeekendAssignments = value;
        }
        private List<Assignment> _WeekendAssignments;

        public List<Assignment> WeeklyAssignments
        {
            get => _WeeklyAssignments; set => _WeeklyAssignments = value;
        }
        private List<Assignment> _WeeklyAssignments;

        public DutyAssignmentEntry()
        {
            _Week = "";
            _Template = -1;
            _WeeklyMode = true;
            _MidweekAssignments = new List<Assignment>();
            _WeekendAssignments = new List<Assignment>();
            _WeeklyAssignments = new List<Assignment>();
        }
    }

    public class Assignment
    {
        [XmlAttribute]
        public int ColumnIndex
        {
            get => _ColumnIndex; set => _ColumnIndex = value;
        }
        private int _ColumnIndex;

        [XmlText]
        public string Name
        {
            get => _Name; set => _Name = value;
        }
        private string _Name;

        public Assignment()
        {
            _ColumnIndex = -1;
            _Name = "";
        }
    }
}

The above is in my DLL library. It gets called from my MFC project. I am just doing tests for now:

void CMSATools::Test2()
{
    if (m_pInterface != nullptr)
    {
        m_pInterface->Test2();
    }
}

When I execute the above MFC code I get a XML file (as I anticipated) like this:

<?xml version="1.0" encoding="utf-8"?>
<DutyAssignmentHistory xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.publictalksoftware.co.uk/msa">
  <DutyAssignments>
    <DutyAssignmentEntry Week="W20180101" Template="-1" WeeklyMode="true">
      <MidweekAssignments />
      <WeekendAssignments />
      <WeeklyAssignments>
        <Assignment ColumnIndex="1">Assign 1</Assignment>
        <Assignment ColumnIndex="2">Assign 2</Assignment>
        <Assignment ColumnIndex="3">Assign 3</Assignment>
        <Assignment ColumnIndex="4">Assign 4</Assignment>
        <Assignment ColumnIndex="5">Assign 5</Assignment>
        <Assignment ColumnIndex="6">Assign 6</Assignment>
      </WeeklyAssignments>
    </DutyAssignmentEntry>
  </DutyAssignments>
</DutyAssignmentHistory>

Notice that I have three potential inner node lists:

  • MidweekAssignments
  • WeekendAssignments
  • WeeklyAssignments

The way I want the XML to serialize is like this:

The WeeklyAssignments node should be present in the XMl entry if WeeklyMode is set to "Weekly" otherwise, the Middweek/Weekend nodes shoudl be present.

At the moment, even if there are no entries in the node list it is creating empty node. It is not critical and it can stay like that. But is it possible to adjust the code so that it only reads/writes if there is data?


Solution

  • Add the [XmlElement] attribute to your properties:

    [XmlElement]
    public List<Assignment> MidweekAssignments
    {
        get { return  _MidweekAssignments; } set {   _MidweekAssignments = value;}
    }
    private List<Assignment> _MidweekAssignments;
    
    [XmlElement]
    public List<Assignment> WeekendAssignments
    {
        get { return _WeekendAssignments; } set {   _WeekendAssignments = value;}
    }
    private List<Assignment> _WeekendAssignments;
    
    [XmlElement]
    public List<Assignment> WeeklyAssignments
    {
        get 
        {  return  _WeeklyAssignments;  } set {   _WeeklyAssignments = value;}
    }
    private List<Assignment> _WeeklyAssignments;
    

    sorry for the non-fancy C#7 syntax, I only have the old thing handy here

    This will not change the interface and its behavior while it will render the XML as follows:

    <?xml version="1.0" encoding="utf-16"?>
    <DutyAssignmentHistory xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.publictalksoftware.co.uk/msa">
      <DutyAssignments>
        <DutyAssignmentEntry Week="W20180101" Template="-1" WeeklyMode="true">
          <WeeklyAssignments ColumnIndex="1">Assign 1</WeeklyAssignments>
          <WeeklyAssignments ColumnIndex="2">Assign 2</WeeklyAssignments>
          <WeeklyAssignments ColumnIndex="3">Assign 3</WeeklyAssignments>
          <WeeklyAssignments ColumnIndex="4">Assign 4</WeeklyAssignments>
          <WeeklyAssignments ColumnIndex="5">Assign 5</WeeklyAssignments>
          <WeeklyAssignments ColumnIndex="6">Assign 6</WeeklyAssignments>
        </DutyAssignmentEntry>
      </DutyAssignments>
    </DutyAssignmentHistory>
    

    and this meets your requirements