locked
Load XML file into either List<MyType> or ObservableCollection<MyType>

    Question

  • I have been searching and trying different suggestions for three days now, full time.  I am building a universal app for Windows 8.1 and Windows Phone 8.1.  The problem, apparently, is that doing this is different between Windows 7.1, Windows Phone 7.1, Windows 8, Windows 8.1, Windows Phone 8.1, and Windows 8.1 universal apps.  You find hundreds of different ways of doing this, none of which work.

    My XML file is of the form:

    <?xml version="1.0" encoding="utf-16"?>
    <ArrayOfPrize xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Prize>
        <PrizeName>Dog</PrizeName>
        <PrizePath>http://www.dog.com</PrizePath>
        <PrizeOwner>System</PrizeOwner>
        <PrizeChecked>false</PrizeChecked>
        <Won>false</Won>
      </Prize>

    </ArrayOfPrize>

    I get errors like you can't do this with non-public types to header is missing, or I get a deaklock.  Every suggestion I have tried has led to either the same error message as the last one, or the same as some previous error message.  Microsoft conveniently left out this common case in their Universal App samples.  Please don't refer me to other articles, because if they were on the internet I have probably already tried them.  Sorry, my frustration is showing through.  Feel free to refer me to other articles.

    Below is my latest attempt that does not work:

    public async Task<T> ReadObjectFromXmlLocalFile(string filename)
    {
     try
     {
      T objectFromXml = default(T);
      XmlSerializer serializer = new XmlSerializer(typeof(T));
      Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
      StorageFile file = await localFolder.GetFileAsync(filename);
      string xmlText = await FileIO.ReadTextAsync(file);
      StringReader reader = new StringReader(xmlText);
      objectFromXml = (T)serializer.Deserialize(reader);
      return objectFromXml;
     }
     catch (Exception e)
     {
      ErrorBucket.AddError(e.Message);
      return default(T);
     }
    }

    Incidentally, every folder path leads to either C:\Data\SharedData... or C:\Data\Users..., which is very strange, since my project is not even on the C drive.  I have recreated the directory structure that it is looking for and put my files there, but eventually this is going to be an issue.  The program used to look in the correct place and then it stopped and starting looking in C:\Data, and I have no idea why.


    Larry Maturo


    • Edited by lmaturo Thursday, April 9, 2015 10:54 PM
    Thursday, April 9, 2015 10:27 PM

Answers

  • Here is the XML file I used to test

    <?xml version="1.0" encoding="utf-8"?>
    <ArrayOfPrize xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Prize>
        <PrizeName>Dog1</PrizeName>
        <PrizePath>http://www.dog.com</PrizePath>
        <PrizeOwner>System</PrizeOwner>
        <PrizeChecked>false</PrizeChecked>
        <Won>false</Won>
      </Prize>
      <Prize>
        <PrizeName>Dog2</PrizeName>
        <PrizePath>http://www.dog.com</PrizePath>
        <PrizeOwner>System</PrizeOwner>
        <PrizeChecked>false</PrizeChecked>
        <Won>false</Won>
      </Prize>
      <Prize>
        <PrizeName>Dog3</PrizeName>
        <PrizePath>http://www.dog.com</PrizePath>
        <PrizeOwner>System</PrizeOwner>
        <PrizeChecked>false</PrizeChecked>
        <Won>false</Won>
      </Prize>
    </ArrayOfPrize>


    jdweng

    • Marked as answer by lmaturo Saturday, April 11, 2015 3:32 PM
    Saturday, April 11, 2015 8:49 AM

All replies

  • I use the following code to read in XML:

     public static async Task<T> LoadObjectFromStorageAsync<T>(string fileName)
                {
                    // this reads XML content from a file ("filename") and returns an object  from the XML
                    StorageFolder folder;
                    try
                    {
                        folder = ApplicationData.Current.LocalFolder;
                        T objectFromXml = default(T);
                        var serializer = new XmlSerializer(typeof(T));                 
                        StorageFile file = await folder.GetFileAsync(fileName);
                        Stream stream = await file.OpenStreamForReadAsync();
                        objectFromXml = (T)serializer.Deserialize(stream);
                        stream.Dispose();
                        return objectFromXml;
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.ToString());
                        return default(T);
                    }
                }

    I believe the folder is correct - ApplicationData is stored within your user folder, not the location of the Visual Studio project.

    If the above code doesn't work, please provide the code for the Prize class. I think my code should work for any XML/class as long as it's not too complex.


    Visit http://blog.grogansoft.com/ for Windows development fun.

    EDIT: Something just occurred to me. I once had an issue that might be the same as yours. I couldn't read multiple objects from an XML file unless they were in a list within the class. Your XML sample doesn't have a top-level above 'Prize' (e.g. <ListOfPrizes>) to contain all the <Prize> objects. Or did you leave that out of your question for brevity? Maybe instead of serializing the list itself, serialise the object containing the list.

    Thursday, April 9, 2015 11:07 PM
  • Oddly, it deadlocks.  I'm going to have to look at that further to see if I can figure out why.

    Larry Maturo

    Friday, April 10, 2015 12:36 AM
  • Try code below.  I always like to write XML from the code before reading.  It is easier to debug the class formats when writing than reading.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Data;
    using System.Xml;
    using System.Xml.Serialization;
    using System.Xml.Schema;
    using System.IO;
    
    namespace XMLSerialize
    {
        class Program
        {
            const string FILENAME = @"c:\temp\test.xml";
            static void Main(string[] args)
            {
                ArrayOfPrize arrayOfPrize = new ArrayOfPrize(){
                    prizes = new List<Prize>(){
                        new Prize(){
                             prizeName = "Dog",
                             prizePath = "http://www.dog.com",
                             prizeOwner = "System",
                             prizeChecked = false,
                             won = false
                        }
                    }
                };
    
                XmlSerializer serializer = new XmlSerializer(typeof(ArrayOfPrize));
    
                StreamWriter writer = new StreamWriter(FILENAME);
                serializer.Serialize(writer, arrayOfPrize);
                writer.Flush();
                writer.Close();
                writer.Dispose();
    
    
                XmlSerializer xs = new XmlSerializer(typeof(ArrayOfPrize));
                XmlTextReader reader = new XmlTextReader(FILENAME);
                ArrayOfPrize newArrayOfPrize = (ArrayOfPrize)xs.Deserialize(reader);
            }
        }
        [Serializable, XmlRoot("ArrayOfPrize")]
        public class ArrayOfPrize
        {
            [XmlElement("Prize")]
            public List<Prize> prizes;
        }
    
        [Serializable, XmlRoot(ElementName = "Prize")]
        public class Prize
        {
            [XmlElement("PrizeName")]
            public string prizeName { get; set; }
            [XmlElement("PrizePath")]
            public string prizePath { get; set; }
            [XmlElement("PrizeOwner")]
            public string prizeOwner { get; set; }
            [XmlElement("PrizeChecked")]
            public Boolean prizeChecked { get; set; }
            [XmlElement("Won")]
            public Boolean won { get; set; }
        }
    
    
    }
    


    jdweng

    Friday, April 10, 2015 1:21 AM
  • Thank you for your help, but it doesn't work with universal apps.  There is no System.Data namespace and Serializable is not recognizable.  It wants to create a new Serializable class for me.  You are working with an extremely limited API for universal apps, which is one of the problems I'm having.  Many of the solutions I come across just will not compile in a universal app.  But again, thank you.


    Larry Maturo

    Friday, April 10, 2015 2:32 PM
  • Just noticed your edit.  I did not leave anything out.  How would I accomplish that?  Something like:

    public class ArrayOfPrize : List<Prize>

    {

    }

    I'll try that. Thank you.  That could well be the problem.


    Larry Maturo

    Friday, April 10, 2015 2:37 PM
  • Did you try to add the namespaces from the menu Project : Add Reference?  Depending on the version of VS some libraries don't automatically come up. Then you have to add the reference.

    jdweng

    Friday, April 10, 2015 3:45 PM
  • There are no references to add.  It automatically adds all references to the project. I tried and it told me they were already loaded.  Because there are so few of them, it's possible for them to load all of them by default.  The references experience in a universal app is nothing like the reference experience in a traditional windows program.

    Larry Maturo

    Friday, April 10, 2015 4:35 PM
  • I finally got it to run, and it returns the correct number of items, but each item in the list is the last item in the file, so in my case I have 109 versions of the same item in the list.  I've run into this result more than once, unfortunately.  I have used the StringReader version of your code so I could verify that Serializer.Deserialize was getting the correct input, and it was.  109 different items goes in, and 109 of the same object comes out.  I now have this class above Prize, and I'm trying to deserialize to it:

        [XmlRoot("ArrayOfPrize")]
        public class ArrayOfPrize : List<Prize>
        {
            public ArrayOfPrize()
            {

            }

            public ObservableCollection<Prize> ToObservableCollection()
            {
                return this.ToObservableCollection();
            }

            public void FromObservableCollection(ObservableCollection<Prize> prizes)
            {
                foreach (Prize prize in prizes)
                {
                    this.Add(prize);
                }
            }

        }

    Is this the correct format for the class above Prize?


    Larry Maturo

    Friday, April 10, 2015 6:56 PM
  • You can't have a List<Prize> as a root in XML.  XML requires a flat root.   See my Serialization classes.  They are correct.

    [Serializable, XmlRoot("ArrayOfPrize")]
        public class ArrayOfPrize
        {
            [XmlElement("Prize")]
            public List<Prize> prizes;
        }
    
        [Serializable, XmlRoot(ElementName = "Prize")]
        public class Prize
        {
            [XmlElement("PrizeName")]
            public string prizeName { get; set; }
            [XmlElement("PrizePath")]
            public string prizePath { get; set; }
            [XmlElement("PrizeOwner")]
            public string prizeOwner { get; set; }
            [XmlElement("PrizeChecked")]
            public Boolean prizeChecked { get; set; }
            [XmlElement("Won")]
            public Boolean won { get; set; }
        }
    


    jdweng

    Friday, April 10, 2015 9:05 PM
  • Okay, I changed ArrayOfPrize and Prize to match yours (with the exception of serializable, which does not exist in universal apps.)  I still get N copies of the last prize in the list instead of N different prizes. I get the same result with a lot of the examples I have tried.  I also get a lot of examples that won't compile in a universal app.  As I had mentioned before, I also get the contents of the file as a string, and pass in a StringReader instead of stream to make sure the input is correct, and it is.  N different prizes in the input and N of the same prize in the output. 


    Larry Maturo

    Friday, April 10, 2015 9:48 PM
  • What is your latest code?

    jdweng

    Friday, April 10, 2015 10:01 PM
  •         public async T LoadObjectFromStorageAsync<T>(string fileName)
            {
                StorageFolder folder;
                try
                {
                    folder = ApplicationData.Current.LocalFolder;
                    string path = folder.Path + '\\' + fileName;
                    T objectFromXml = default(T);
                    XmlSerializer serializer = new XmlSerializer(typeof(T));
                    StorageFile file = await folder.GetFileAsync(fileName);
                    Stream stream = await file.OpenStreamForReadAsync();
                    objectFromXml = (T)serializer.Deserialize(stream);
                    stream.Dispose();
                    return objectFromXml;
                }
                catch (Exception ex)
                {
                    ErrorBucket.AddError(ex.Message);
                    return default(T);
                }
            }

    Larry Maturo

    Friday, April 10, 2015 10:21 PM
  • You are making this problem a lot harder than it actually is.  I gave you all the pieces to the solution.  First, ArrayOfPrize the root of the XML is not an array. Prize is the array. You don't need a generic T since there is already a defined class ArrayOfPrize. I think you can adapt the code below which works with VS.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Xml;
    using System.Xml.Serialization;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                MyXML myXML = new MyXML();
                ArrayOfPrize arrayOfPrize = myXML.LoadObjectFromStorageAsync();
            }
        }
        public class MyXML
        {
            public ArrayOfPrize LoadObjectFromStorageAsync()
             {
                 try
                 {
    
                     XmlSerializer serializer = new XmlSerializer(typeof(ArrayOfPrize));
    
                     Stream stream = File.Open(@"c:\temp\test.xml", FileMode.Open,FileAccess.Read);
                     ArrayOfPrize objectFromXml = (ArrayOfPrize)serializer.Deserialize(stream);
                     stream.Dispose();
                     return objectFromXml;
                 }
                 catch (Exception ex)
                 {
                     Console.WriteLine(ex.Message);
                 }
                 return null;
             }
        }
        [XmlRoot("ArrayOfPrize")]
        public class ArrayOfPrize
        {
            [XmlElement("Prize")]
            public List<Prize> prizes {get; set;}
        }
    
        [XmlRoot(ElementName = "Prize")]
        public class Prize
        {
            [XmlElement("PrizeName")]
            public string prizeName { get; set; }
            [XmlElement("PrizePath")]
            public string prizePath { get; set; }
            [XmlElement("PrizeOwner")]
            public string prizeOwner { get; set; }
            [XmlElement("PrizeChecked")]
            public Boolean prizeChecked { get; set; }
            [XmlElement("Won")]
            public Boolean won { get; set; }
        }
    }


    jdweng


    Saturday, April 11, 2015 8:33 AM
  • Here is the XML file I used to test

    <?xml version="1.0" encoding="utf-8"?>
    <ArrayOfPrize xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Prize>
        <PrizeName>Dog1</PrizeName>
        <PrizePath>http://www.dog.com</PrizePath>
        <PrizeOwner>System</PrizeOwner>
        <PrizeChecked>false</PrizeChecked>
        <Won>false</Won>
      </Prize>
      <Prize>
        <PrizeName>Dog2</PrizeName>
        <PrizePath>http://www.dog.com</PrizePath>
        <PrizeOwner>System</PrizeOwner>
        <PrizeChecked>false</PrizeChecked>
        <Won>false</Won>
      </Prize>
      <Prize>
        <PrizeName>Dog3</PrizeName>
        <PrizePath>http://www.dog.com</PrizePath>
        <PrizeOwner>System</PrizeOwner>
        <PrizeChecked>false</PrizeChecked>
        <Won>false</Won>
      </Prize>
    </ArrayOfPrize>


    jdweng

    • Marked as answer by lmaturo Saturday, April 11, 2015 3:32 PM
    Saturday, April 11, 2015 8:49 AM
  • Hi Joel,

    Weird.  I had replied, and before I pressed the submit button I marked it as the answer, and it lost my reply.  Anyway, I copied your code almost exactly, and it was very concise code.  Where I differed was that the properties in Prize were all dependency properties. That again got me 109 copies of the last prize.  So, I decided to get rid of the dependency properties to just make sure I was duplicating your code 100%, and that worked.  I have absolutely no idea why you can't deserialize a class with dependency properties. Anyway, thank you!!!  I would never have solved this without your last reply.  Because it was so concise, it removed all the clutter that was keeping me from seeing what the real problem was.


    Larry Maturo

    Saturday, April 11, 2015 3:43 PM
  • You kink of forced me to make the code concise because I wasn't sure what would work in the universal app.

    jdweng

    Saturday, April 11, 2015 6:53 PM