Peter Howe
Peter Howe

Reputation: 429

Simplest way to duplicate a pre-existing XML format in C#

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.

Upvotes: 0

Views: 84

Answers (1)

dbc
dbc

Reputation: 116805

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).

Upvotes: 1

Related Questions