Problem
I need to build the following XML document
<?xml version="1.0"?>
<SFDocument>
<TableID>5415</TableID>
<PageSize>1</PageSize>
<PageIndex>1</PageIndex>
<MultiCompany>No</MultiCompany>
<Language>1033</Language>
<Fields>
<Field>Operation No.</Field>
<Field>Line No.</Field>
<Field>Date</Field>
<Field>Comment</Field>
</Fields>
<TableFilters>
<TableFilter>
<Field>Status</Field>
<Filter>3</Filter>
</TableFilter>
<TableFilter>
<Field>Prod. Order No.</Field>
<Filter>101006</Filter>
</TableFilter>
</TableFilters>
<Key>
<Field>Routing Reference No</Field>
<Field>10000</Field>
</Key>
</SFDocument>
using the following code, it works but is it the proper way of doing it.
internal class Field
{
public string Name { get; set; }
public const string FieldName = "Field";
}
internal class Key
{
public string Field { get; set; }
public const string FieldName = "Field";
}
internal class TableFilter
{
public string Field { get; set; }
public string Filter { get; set; }
public const string FieldName = "Field";
public const string FilterName = "FilterName";
}
void Main()
{
//this part is just for the example, obviously, this would be passed as parameter
var tableId = 5415;
var pageSize = 1;
var pageIndex = 1;
var multiCompany = "No";
var language = 1033;
var fields = new List<Field>();
fields.Add(new Field() {Name = "Operation No."});
fields.Add(new Field() {Name = "Line No."});
fields.Add(new Field() {Name = "Date"});
fields.Add(new Field() {Name = "Comment"});
var tableFilters = new List<TableFilter>();
tableFilters.Add(new TableFilter() {Field = "Status", Filter = "3"});
tableFilters.Add(new TableFilter() {Field = "Prod. Order No.", Filter = "101006"});
var keys = new List<Key>();
keys.Add(new Key() {Field = "Routing Reference No"});
keys.Add(new Key() {Field = "10000"});
//End of setup
XDocument doc = new XDocument(new XElement("SFDocument",
new XElement("TableId", tableId),
new XElement("PageSize", pageSize),
new XElement("PageIndex", pageIndex),
new XElement("MultiCompany", multiCompany),
new XElement("Language", language),
new XElement("Fields"),
new XElement("TableFilters"),
new XElement("Key")
)
);
var xFields = doc.Element("SFDocument").Element("Fields");
foreach (Field field in fields)
{
xFields.Add(new XElement(Field.FieldName, field.Name));
}
var xTableFilters = doc.Element("SFDocument").Element("TableFilters");
foreach (TableFilter tableFilter in tableFilters)
{
var tf = new XElement("TableFilter");
tf.Add(new XElement(TableFilter.FieldName, tableFilter.Field));
tf.Add(new XElement(TableFilter.FilterName, tableFilter.Filter ));
xTableFilters.Add(tf);
}
var xKey = doc.Element("SFDocument").Element("Key");
foreach (Key key in keys)
{
xKey.Add(new XElement(Key.FieldName, key.Field));
}
doc.Dump(); //Using LinqPad
}
Solution
I ended up changing my approach and went with a serialize method:
public static class SerializationHelper
{
internal static string SerializeObject<T>(this T toSerialize)
{
var xmlSerializer = new XmlSerializer(toSerialize.GetType());
var textWriter = new StringWriter();
xmlSerializer.Serialize(textWriter, toSerialize);
return textWriter.ToString();
}
}
[Serializable]
public class SFDocument
{
public int TableId { get; set; }
public int PageSize { get; set; }
public int PageIndex { get; set; }
public string MultiCompany { get; set; }
public int Language { get; set; }
[XmlArrayItem("Field")]
public List<string> Fields { get; set; }
public List<TableFilter> TableFilters { get; set; }
[XmlArrayItem("Field")]
public List<string> Key { get; set; }
public SFDocument(): this(5415, 1, 1, "No", 1033){}
public SFDocument(int tableId, int pageSize, int pageIndex, string multiCompany, int language)
{
TableId = tableId;
PageSize = pageSize;
PageIndex = pageIndex;
MultiCompany = multiCompany;
Language = language;
Fields = new List<string>();
Key = new List<string>();
TableFilters = new List<TableFilter>();
}
}
[Serializable]
public class TableFilter
{
public string Field { get; set; }
public string Filter { get; set; }
public TableFilter() {}
public TableFilter(string field, string filter)
{
Field = field;
Filter = filter;
}
}
void Main()
{
var sfd = new SFDocument();
sfd.Fields.Add("Field1");
sfd.Fields.Add("Field2");
sfd.Fields.Add("Field3");
sfd.Fields.Add("Field4");
sfd.Key.Add("Key1");
sfd.Key.Add("Key2");
sfd.Key.Add("Key3");
sfd.TableFilters.Add(new TableFilter("field1", "filter1"));
sfd.TableFilters.Add(new TableFilter("field2", "filter2"));
sfd.TableFilters.Add(new TableFilter("field3", "filter3"));
sfd.SerializeObject().Dump();
}
Using XML serialization is certainly a valid approach. But if you wanted to keep using LINQ to XML, I would change the code to be more declarative. This way, the structure of the code would closely resemble the structure of the XML, so it would be clearer to see what’s going on:
XDocument doc = new XDocument(
new XElement(
"SFDocument",
new XElement("TableId", tableId),
new XElement("PageSize", pageSize),
new XElement("PageIndex", pageIndex),
new XElement("MultiCompany", multiCompany),
new XElement("Language", language),
new XElement(
"Fields", fields.Select(field => new XElement(Field.FieldName, field.Name))),
new XElement(
"TableFilters",
tableFilters.Select(
tableFilter =>
new XElement(
"TableFilter",
new XElement(TableFilter.FieldName, tableFilter.Field),
new XElement(TableFilter.FilterName, tableFilter.Filter)))),
new XElement("Key", keys.Select(key => new XElement(Key.FieldName, key.Field)))));
Though the TableFilters
element is probably too complicated for this approach. But you can easily refactor that to another method:
private static XElement CreateTableFilters(IEnumerable<TableFilter> tableFilters)
{
return new XElement(
"TableFilters",
tableFilters.Select(
tableFilter =>
new XElement(
"TableFilter",
new XElement(TableFilter.FieldName, tableFilter.Field),
new XElement(TableFilter.FilterName, tableFilter.Filter))));
}
…
XDocument doc = new XDocument(
new XElement(
"SFDocument",
new XElement("TableId", tableId),
new XElement("PageSize", pageSize),
new XElement("PageIndex", pageIndex),
new XElement("MultiCompany", multiCompany),
new XElement("Language", language),
new XElement(
"Fields", fields.Select(field => new XElement(Field.FieldName, field.Name))),
CreateTableFilters(tableFilters),
new XElement("Key", keys.Select(key => new XElement(Key.FieldName, key.Field)))));
Back when using .NET 2.0, I wrote a Tag
class which inherited from XmlElement
, and a XmlBuilder
which inherited from XmlDocument
. For legacy reasons we still use it, but I’m pretty sure it could be rewritten in half the code as a few extension methods.
Here’s a small sample:
XmlBuilder xml = new XmlBuilder("AmazonEnvelope");
xml.AddNamespace("noNamespaceSchemaLocation", "amzn-envelope.xsd");
XmlBuilder.Tag message = xml.FirstTag.AddTag("Message");
message.AddTag("MessageID", messageID++);
message.AddTag("OperationType", "PartialUpdate");
XmlBuilder.Tag product = message.AddTag("Product");
product.AddTag("SKU", part.ID);
if (part.upcCode.Length == 12)
{
XmlBuilder.Tag UPC = product.AddTag("StandardProductID");
UPC.AddTag("Type", "UPC");
UPC.AddTag("Value", part.upcCode);
}
else if (part.upcCode.Length == 13)
{
XmlBuilder.Tag UPC = product.AddTag("StandardProductID");
UPC.AddTag("Type", "EAN");
UPC.AddTag("Value", part.upcCode);
}
product.AddTag("ProductTaxCode", "A_GEN_TAX");
return xml.OuterXml
The XmlBuilder
constructor just takes the string and uses that to create the first node:
_outerNode = new Tag(qualifiedName, this);
AppendChild(_outerNode);
xml.FirstTag
is just a call to get _outerNode
as a Tag
(instead of an XmlElement
).
And so on…
This lets you easily add a lot of logic (“Do I add this set of tags?”), doesn’t require a multitude of new
s, and you can go back and add more things to a tag later.