简体   繁体   中英

How to store C# object data as XML using XML Serialization

Could somebody advise if XML Serialization is achievable with my design?

I have written the code for the core functionality for my 'System Administration' application. For Sprint 1 of my project, I have to store all data in a temporary XML data store which will be replaced using ADO.Net in Sprint 2.

I have looked into serializing classes using XML Serialization such as in below articles: https://www.codeproject.com/Articles/483055/XML-Serialization-and-Deserialization-Part https://www.codeproject.com/Articles/487571/XML-Serialization-and-Deserialization-Part-2

I have the following classes which (I think) need to be stored as XML:

  • A User class with multiple properties
  • A UserAccessGroup class with a String property Name and a List of ints UserIDs
  • A UserAdministration Class that holds a List of all User objects and one or more UserAccessGroup objects (constructor uses params UserAccessGroup[]). It has methods for adding/deleting users on the system, adding/deleting UserIDs from a UserAccessGroup.
  • A UserVerification Class that holds a UserAdministration object and the UserAccessGroup object for the application being logged on to. It also has two ints for counting failed and successful logon attempts a method for logging on which returns true if all conditions are met, otherwise false.
  • A ServiceRequest class with multiple properties
  • A ServiceRequestTracker class that holds a list of all ServiceRequest objects and has methods for adding/deleting/editing them.

I have the below code in my Program file:

static void Main()
        {
            UserAccessGroup SystemAdmin_App = new UserAccessGroup("Admin Operators");
            UserAccessGroup Shareholder_App = new UserAccessGroup("Shareholders");
            UserAccessGroup Broker_App = new UserAccessGroup("Brokers");
            UserAccessGroup StockExMgr_App = new UserAccessGroup("StockExMgrs");

            UserIDGenerator IDGenerator = new UserIDGenerator();

            UserAdministration userAdmin = new UserAdministration(IDGenerator,
                SystemAdmin_App, Shareholder_App, Broker_App, StockExMgr_App);

            UserVerificationService userVerification = new UserVerificationService(
                userAdmin, SystemAdmin_App);

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            LoginScreen myLoginScreen = new LoginScreen();
            Application.Run(myLoginScreen);
        }

Only now that I have finished writing all of my classes using TDD have I looked into storing object data as XML. I am finding it very confusing to apply XML Serialization to my design and I am wondering if it is possible to achieve this without completely re-designing my application from scratch?

Any help here would be greatly appreciated!

As per the discussion in comments, I'll give you a basic overview of how you can quickly (de)serialize custom classes.

For reference, here's your comment to which I'm replying:

@Flater Thanks for the reply! I'm still unsure how to do serialize my objects. For example, UserAdministration contains a list of Users and has methods for adding/removing Users from the list. Say for example, I add a new user by calling the addUser method of UserAdministration. How does the new User object get added to the XML file?


I always use this helper class because it makes the code so much cleaner. I've copied it from the internet somewhere, potentially StackOverflow. I will credit the author once I know/remember who it is.

public static class SerializerHelper
{
    /// <summary>
    /// Serializes an object.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="serializableObject"></param>
    /// <param name="fileName"></param>
    public static void SerializeObject<T>(string filepath, T serializableObject)
    {
        if (serializableObject == null) { return; }

        try
        {
            XmlDocument xmlDocument = new XmlDocument();
            XmlSerializer serializer = new XmlSerializer(serializableObject.GetType());
            using (MemoryStream stream = new MemoryStream())
            {
                serializer.Serialize(stream, serializableObject);
                stream.Position = 0;
                xmlDocument.Load(stream);
                xmlDocument.Save(filepath);
                stream.Close();
            }
        }
        catch (Exception ex)
        {
            //Log exception here
        }
    }


    /// <summary>
    /// Deserializes an xml file into an object list
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="fileName"></param>
    /// <returns></returns>
    public static T DeSerializeObject<T>(string filepath)
    {
        T objectOut = default(T);

        if (!System.IO.File.Exists(filepath)) return objectOut;

        try
        {
            string attributeXml = string.Empty;

            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(filepath);
            string xmlString = xmlDocument.OuterXml;

            using (StringReader read = new StringReader(xmlString))
            {
                Type outType = typeof(T);

                XmlSerializer serializer = new XmlSerializer(outType);
                using (XmlReader reader = new XmlTextReader(read))
                {
                    objectOut = (T)serializer.Deserialize(reader);
                    reader.Close();
                }

                read.Close();
            }
        }
        catch (Exception ex)
        {
            //Log exception here
        }

        return objectOut;
    }
}

Before I get to the usage examples, some tips so you know how to approach this:

  • These methods will serialize a single object (of any type) to a given file. This can be a class Foo , a List<Foo> , or a custom made class that contains all the data you want to save MyFooData
  • You cannot add to an existing file. When saving a file, the old file will be overwritten . I have not yet come across a use case where I needed to add to a file and couldn't just read the file's contents, change them, and store the whole object again. But I've only used it for small scale storage.
  • XML serialization uses public properties and a parameterless constructor . So make sure the objects you want to store have both of these. It will not serialize private or protected properties; or public/private/protected class fields.
  • The XML Serializer will automatically also serialize subclasses that are contained within (if they are public properties!). As far as I'm aware, it can go as deep as you want it to go. It will save everything .
  • The XML serializer has a weakness to recursion. I will explain how to avoid recursion in the code examples.
  • XML Serialization cannot (de)serialize Dictionary<T1,T2> or any IEnumerable that uses hashes for storage. However, if you want to store a Dictionary<T1,T2> , you can store it as a List<KeyValuePair<T1,T2>> , which means it will retain your data; just not the internal hash that it uses for quick lookup.

1 - The simplest example

[Serializable]
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public static void TestMethod()
{
    var myUser = new User() { Name = "John", Email = "John@john.com" };

    //Save to file
    SerializerHelper.SerializeObject(@"C:\MyDir\MyFile.txt", myUser);

    //Read from file
    var myUserReloaded = SerializerHelper.DeSerializeObject<User>(@"C:\MyDir\MyFile.txt");
}

Take note of the [Serializable] attribute on the User class. This is usually the only change that you need to make to your classes to make this work.

2 - Avoiding recursion and stack overflows

As mentioned in the tips, this serialization has a weakness to recursion. This can happen when classes refer to eachother in both directions.

[Serializable]
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }

    public Manager Boss {get; set; }
}

[Serializable]
public class Manager
{
    public string Name { get; set; }

    public User FavoriteEmployee {get; set; }
}

public static void TestMethod()
{
    var userJohn = new User() { Name = "John", Email = "John@john.com" };
    var managerMark = new Manager() { Name = "Mark" };

    managerMark.FavoriteEmployee = userJohn;
    userJohn.Boss = managerMark;

    //Save to file
    SerializerHelper.SerializeObject(@"C:\MyDir\MyFile.txt", userJohn);

    //Read from file
    var userJohnReloaded = SerializerHelper.DeSerializeObject<User>(@"C:\MyDir\MyFile.txt");
}

You will get a stack overflow exception when saving the file, because the serializer gets stuck in an infinite loop.

The serializer tries to write all of userJohn 's properties to the file. When it gets to the Boss property, it notices that it is a serializable object and will begin serializing all of managerMark 's properties. When it gets to the FavoriteEmployee property, it notices that it is a serializable object and will begin serializing all of userJohn 's properties. When it...

You can prevent this from happening by using the [XmlIgnore] attribute on one of the two properties (or both, if you want to be really secure).

[Serializable]
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }

    public Manager Boss {get; set; }
}

[Serializable]
public class Manager
{
    public string Name { get; set; }

    [XmlIgnore]
    public User FavoriteEmployee {get; set; }
}

The serializer tries to write all of userJohn 's properties to the file. When it gets to the Boss property, it notices that it is a serializable object and will begin serializing all of managerMark 's properties. When it gets to the FavoriteEmployee property, it notices that this property is marked [XmlIgnore] and it will not attempt to serialize what is contained within.


I hope this answer is what you were looking for.


EDIT I forgot a big caveat.

Let's say we are storing a List<Child> with two Child objects in it. Both Child objects have a Parent property, and it just so happens that both children are referencing the same Parent (same object in memory).

If I store the Parent (including a list of its children) it will serialize our Parent which will contain both Child objects. When deserializing, you will again have a singular Parent object and the two Child objects you started with.

If I store the List<Child> (including their parent), it will serialize the Parent for both Child objects. However, when deserializing, both Parent objects will be deserialized but they will be separate objects in memory .

As you can see, there could be a potential bug here if you are expecting both children to refer to the same Parent (as an object in memory). For this reason, I have a personal rule about how to use the serialization, and where to put the [XmlIgnore] attribute.

Children ( IEnumerable<Foo> ) get serialized, parents ( Foo ) do not. This allows me to store an entire tree in one go.

This means that I have to store the parent (and automatically have its children be serialized too), and I can only store the child without its reference to its parent (unless you store a ParentId key in the Child ).

By doing it this way, you ensure that you won't create a multitude of Parent objects through deserialization, because every object only gets mentioned once in the XML file.


Edit

I forgot to add the SerializerHelper. to the method calls. Fixed now.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM