views:

272

answers:

2

I've got a Stream containing xml in the following format that I want to deserialize into C# objects

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<OrganisationMetaData xmlns="urn:organisationMetaDataSchema">
   <Organisations>
     <Organisation>
       <Code>XXX</Code>
       <Name>Yyyyyy</Name>...

I've done this loads of times with strings, but with the stream it is kindly appending the namespace attribute to all the complex elements. If I just remove the xmlns attribute, and forget about validating it against a schema, it just appends an empty xmlns attribute. The problem I have is that the Deserialize method in XmlSerializer (?), throws an error saying it doesn't expect the attribute. I have tried decorating the class with the XmlRoot and XmlType attributes but this didn't change anything.

Here's the class I want to deserialize into

[XmlRoot(
   ElementName = "OrganisationMetaData", 
   Namespace = "urn:organisationMetaDataSchema")]
public class OrganisationMetaData
{
    public List<Organisation> Organisations { get; set; }
}

[XmlType(
   TypeName = "Organisation", 
   Namespace = "urn:organisationMetaDataSchema")]
public class Organisation
{
   public string Code {get; set;}

   public string Name {get; set;}
}

Here's the method that is being used to do the work

 public IList<Organisation> DeserializeOrganisations(Stream stream)
    {
        var serializer = new XmlSerializer(typeof(OrganisationMetaData));

        var mappingAssembly = //Resource in another assembly

        var schemas = new XmlSchemaSet();
        schemas.Add(
            "urn:organisationMetaDataSchema",
            XmlReader.Create(
                mappingAssembly.GetManifestResourceStream(
                    // An xml schema
                    )
                )
            );
        var settings = new XmlReaderSettings()
                           {
                               ValidationType = ValidationType.Schema,
                               Schemas = schemas,
                               ValidationFlags =
                     XmlSchemaValidationFlags.ReportValidationWarnings
                           };            

        settings.ValidationEventHandler += settings_ValidationEventHandler;
        var reader = XmlReader.Create(stream, settings);

        var metaData= (OrganisationMetaData)serializer.Deserialize(reader);
        return metaData.Organisations.ToList();
    }

I've tried this using DataContractSerializer but that brings it's own oppotunities to learn, so if anyone could help with what I ought to be putting in the attributes to get XmlSerializer to work, it would be great.

Any help would be appreciated, thanks.

+1  A: 

The key here is that the [XmlRoot] can only be applied to a root type such as a class; if you are using a List<> as the root it won't work - but we can shim that with [XmlElement]. I'm using the Stream approach (via Encoding.UTF8), but note that this isn't really the heart of the issue IMO (the root type is):

[XmlRoot(Namespace="urn:organisationMetaDataSchema")]
public class Organisations
{
    private readonly List<Organisation> items = new List<Organisation>();
    [XmlElement("Organisation")]
    public List<Organisation> Items { get { return items; } }

}
public class Organisation
{
    public string Code { get; set; }
    public string Name { get; set; }
}
static class Program
{
    static void Main()
    {
        string xml = @"<?xml version='1.0' encoding='utf-8' standalone='yes'?><Organisations xmlns='urn:organisationMetaDataSchema'><Organisation><Code>XXXX</Code><Name>YYYYYYYY</Name></Organisation></Organisations>";
        XmlSerializer ser = new XmlSerializer(typeof(Organisations));
        using (Stream input = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
        {
            Organisations orgs = (Organisations)ser.Deserialize(input);
        }
    }
}
Marc Gravell
Thanks Marc, I'm having a bit of brain fade on this now. My xml doesn't have the single quotes that your string has. How would you overcome this, something as simple as Replace(...) ?
Mark Dickinson
I've tried just about all I can think of here, so after about 8 hours, I've given up and gone for DataContractSerializer instead, which at east can tell me a bit more about what it doesn't like about the xml. Thanks for your help Marc :)
Mark Dickinson
@Mark - the single quote doesn't matter. The code as posted should deserialize the xml you have cited. Can you post (or e-mail if you like) your classes at all? I'm pretty sure I can tell you what is wrong...
Marc Gravell
@Marc - I've got a few constraints here wrt. posting the code out, but i'll cobble something together just to get a decent answer to this. The problem I found was that if I got my stream into a string it had escape characters before the double quotes, newlines, and returns. I have no idea where these came from. I've moved on to using DataContractSerializer, but this has a problem with the Organisations object, even if I wrapped it in another object.
Mark Dickinson
@Marc - I've switched over to DataContractSerializer, it might help me out longer term too, after finding this answer ( http://stackoverflow.com/questions/1651010 ), I got it working nicely. If you want to continue with the issue with XmlSerializer and you need anything from me please let me know. Cheers
Mark Dickinson
A: 

I ended up changing the code to use data contract serializer, it gave me more obvious errors around namespace that allowed me to have a reader that validated the xml stream against the schema, and then rewind the stream and use it again in another reader that deserialized the xml.

Reading this question alerted me to a gotcha where xml elements are required to be in alphabetical order. I also found that when deserializing a property of my class that was an enum, I need to require this to be present (it is not nullable after all).

This then caused another error where I had an xml node with some values omitted (ok by my schema), however the data contract expected these to be in sequence so I had to specify this explicitly.

I ended up with a data member attribute like this

[DataMember(
        Name = "MyEnumType", 
        EmitDefaultValue = false, 
        IsRequired = true, 
        Order = 3)] 
//Just assume I added this prop after my Code, and Name properties from above

Thanks to Marc for taking time to look at this.

Mark Dickinson