0

I am working in C#, Visual Studio 2015, targeting .NET 4.5. We have existing systems (some written in Java, some legacy code in C++, etc.) that already exchange XML in particular formats. I have studied XMLSerializer, DataContractSerializer, and looked briefly at the ISerializable Interface. While I think I already know the answer (ISerializable), I figured I would see if anyone has any clever ideas or solutions for duplicating the XML format we need without coding it all ourselves. We have four different XML messages to duplicate, here is just one of them:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE message SYSTEM "work_request_20.dtd">
<message message_id="1005" message_dt="01/21/2008 09:50:23.221 AM" message_type="Work_Request" message_sub_type="New" message_dtd_version_number="2.0">
<header>
    <from_application_id>3367e115-c873-4ac9-a1dd-7e45231dc3d5</from_application_id>
    <to_application_id>35e0cca2-e423-4ffe-ba07-7d056775c228</to_application_id>
</header>
<body>
    <work_request requisition_number="REQ44">
        <client client_id="44">
              <first_name>Barak</first_name>
              <last_name>Obama</last_name>
        </client>
        <patient patient_id="4444" patient_species="CANINE" patient_gender="MALE_INTACT">
            <patient_name>Bo</patient_name>
              <patient_breed>Portuguese Water Dog</patient_breed>
              <patient_birth_dt>04/04/2004</patient_birth_dt>
              <patient_weight patient_weight_uom="lbs">
                    <weight>44.4</weight>
              </patient_weight>
        </patient>
        <doctor>
            <first_name>Surgeon</first_name>
            <last_name>General</last_name>
        </doctor>    
        <service_add>
            <service_cd>ALB</service_cd>
            <service_cd>GLU</service_cd>
            <service_cd>BUN</service_cd>
        </service_add>
    </work_request>
</body>
</message>

If anyone can suggest any brilliant shortcuts as compared to the obvious solution, we would be forever grateful.

Thanks in advance.

dbc
  • 104,963
  • 20
  • 228
  • 340
Peter Howe
  • 429
  • 6
  • 19
  • 1
    if work_request_20.dtd is real and not just cargo culting, you can generate an xsd from the dtd and then classes from the xsd. –  Oct 10 '16 at 17:59
  • `ISerializable` is not the answer. `IXmlSerializable` might be an answer, albeit one requiring maximum work. Have you tried generating c# classes with [xsd.exe](https://msdn.microsoft.com/en-us/library/x6c1kb0s.aspx) or by uploading to http://xmltocsharp.azurewebsites.net/ and then serializing with `XmlSerializer`? That would be a lot easier. – dbc Oct 10 '16 at 18:01
  • By the way, your XML is missing a closing tag ``. May I assume that's a typo? – dbc Oct 10 '16 at 18:05
  • Yes, just a copy/paste error. I have used xsd.exe but the generated code was pretty "thick," I was hoping to NOT have to include the 2500 lines it generated, but that may definitely be less work and maintenance going forward than the ISerializable interface. I was just checking to see if there was some magic out there I had missed. :-) – Peter Howe Oct 10 '16 at 18:18
  • Duh! I meant IXmlSerializable, not ISerializable. I seem to be having a lot of typing difficulties today. – Peter Howe Oct 10 '16 at 18:27
  • Visual Studio 2015 has a Paste Special -> XML to Classes option to generate data classes from XML. – PMV Oct 10 '16 at 18:39
  • I like using XML linq using newXElement to do small xml files. it depends on where the data comes from which is better method. If you don't have the classes already in the code then using xml Iinq is about the same amount of work writing the data to classes as XElement and then you save the step of serializing (instead just writing the results to a file). – jdweng Oct 10 '16 at 20:14

1 Answers1

1

Serializing with XmlSerializer is likely going to be the easiest solution. If classes auto-generated by xsd.exe are too bloated, you can use another code generation tool such as https://xmltocsharp.azurewebsites.net/ to generate them for you -- or even do it yourself manually.

For instance, I generated the following types using https://xmltocsharp.azurewebsites.net/ then made a few manual tweaks that are mentioned in comments:

[XmlRoot(ElementName="header")]
public class Header {

    // I modified the types of these properties from string to Guid
    [XmlElement(ElementName="from_application_id")]
    public Guid From_application_id { get; set; }
    [XmlElement(ElementName="to_application_id")]
    public Guid To_application_id { get; set; }

}

[XmlRoot(ElementName="client")]
public class Client {
    [XmlElement(ElementName="first_name")]
    public string First_name { get; set; }
    [XmlElement(ElementName="last_name")]
    public string Last_name { get; set; }
    [XmlAttribute(AttributeName="client_id")]
    public string Client_id { get; set; }
}

[XmlRoot(ElementName="patient_weight")]
public class Patient_weight {
    // I changed weight from string to decimal
    [XmlElement(ElementName="weight")]
    public decimal Weight { get; set; }
    [XmlAttribute(AttributeName="patient_weight_uom")]
    public string Patient_weight_uom { get; set; }
}

[XmlRoot(ElementName="patient")]
public class Patient {
    [XmlElement(ElementName="patient_name")]
    public string Patient_name { get; set; }
    [XmlElement(ElementName="patient_breed")]
    public string Patient_breed { get; set; }
    [XmlElement(ElementName="patient_birth_dt")]
    public string Patient_birth_dt { get; set; }
    [XmlElement(ElementName="patient_weight")]
    public Patient_weight Patient_weight { get; set; }
    [XmlAttribute(AttributeName="patient_id")]
    public string Patient_id { get; set; }
    [XmlAttribute(AttributeName="patient_species")]
    public string Patient_species { get; set; }
    [XmlAttribute(AttributeName="patient_gender")]
    public string Patient_gender { get; set; }
}

[XmlRoot(ElementName="doctor")]
public class Doctor {
    [XmlElement(ElementName="first_name")]
    public string First_name { get; set; }
    [XmlElement(ElementName="last_name")]
    public string Last_name { get; set; }
}

[XmlRoot(ElementName="work_request")]
public class Work_request {
    [XmlElement(ElementName="client")]
    public Client Client { get; set; }
    [XmlElement(ElementName="patient")]
    public Patient Patient { get; set; }
    [XmlElement(ElementName="doctor")]
    public Doctor Doctor { get; set; }

    // I simplied this into a list of strings.
    [XmlArray(ElementName="service_add")]
    [XmlArrayItem("service_cd")]
    public List<string> Service_add { get; set; }

    [XmlAttribute(AttributeName="requisition_number")]
    public string Requisition_number { get; set; }
}

[XmlRoot(ElementName="body")]
// I renamed this to WorkRequestBody
public class WorkRequestBody 
{
    [XmlElement(ElementName="work_request")]
    public Work_request Work_request { get; set; }
}

[XmlRoot(ElementName="message")]
// I made this generic to account for multiple types of messge.
public class Message<T> where T : class, new()
{
    [XmlElement(ElementName="header")]
    public Header Header { get; set; }
    [XmlElement(ElementName="body")]
    public T Body { get; set; }
    [XmlAttribute(AttributeName="message_id")]
    public string Message_id { get; set; }
    [XmlAttribute(AttributeName="message_dt")]
    public string Message_dt { get; set; }
    [XmlAttribute(AttributeName="message_type")]
    public string Message_type { get; set; }
    [XmlAttribute(AttributeName="message_sub_type")]
    public string Message_sub_type { get; set; }
    [XmlAttribute(AttributeName="message_dtd_version_number")]
    public string Message_dtd_version_number { get; set; }
}

Using these types, I can now deserialize and re-serialize your XML into a Message<WorkRequestBody> and the resulting re-serialized XML is equivalent to the original XML, according to XNode.DeepEquals(). Sample fiddle.

To include a <!DOCTYPE ...> in the re-serialized XML, see this question.

Implementing IXmlSerializable for the root object is roughly the same difficulty as manually reading and writing your entire object graph with an XmlReader and XmlWriter. It's certainly possible but will require more work that using XmlSerializer. You'll still need to design POCOs to hold the data in memory, so it will be easier to use a serializer to read and write those POCOs automatically whenever possible. See here for a guide on how to do it correctly.

Reading and writing with LINQ to XML would represent an intermediate level of difficulty.

Finally, DataContractSerializer is not appropriate since there is no way to indicate that certain c# properties should be serialized as XML attributes (source).

dbc
  • 104,963
  • 20
  • 228
  • 340