Removing empty fields from EPiServer XForm e-mails

A customer asked me to see what I could do for their XForm order notification e-mails today. Apparently the department handling their incoming orders was receiving really long e-mails with loads of superfluous information; this as every field in the XForm was being submitted to them, and not just the ones containing data. This is the default behaviour of the XForm functionality, which of course can be changed with a short bit of code.

XForm.ascx

<XForms:XFormControl ID="xForm" runat="server" ValidationGroup="XForm" />

The only thing that one has to do is attach a short event handler to the BeforeSubmitPostedData event, and then let it filter out all of the unwanted data before allowing everything to proceed as usual.

XForm.ascx.cs

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    xForm.BeforeSubmitPostedData += xForm_BeforeSubmitPostedData;
private void xForm_BeforeSubmitPostedData(object sender, SaveFormDataEventArgs e)
{
    var clone = e.FormData.Data.Clone();
    RemoveEmptyNodes(clone, e.FormData.Data);
}

The SaveFormDataEventArgs object contains the information being submitted by the user. Since we cannot loop through it and remove items from it at the same time, we first need to create a clone of the SerializableXmlDocument contained within (26). We will then use the copy for looping, while at the same time removing the unwanted XmlNodes from the original SerializableXmlDocument.

private static void RemoveEmptyNodes(XmlNode clone, XmlNode original)
{
    if (clone == null || !clone.HasChildNodes) return;
    foreach (XmlNode childNode in clone.ChildNodes)
    {
        RemoveEmptyNodes(childNode, original);
        if (!string.IsNullOrEmpty(childNode.InnerText)) continue;
        RemoveNodeByName(childNode.Name, original);
    }
}

The method RemoveEmptyNodes(..) above recursively moves through every node in the clone, looking for those without content. When it finds one, it passes the name of this node to another recursive method; RemoveNodeByName(..). Even though the names, attributes and values of the nodes in the two SerializableXmlDocuments are identical, they are still completely different objects; hence, we cannot feed an XmlNode from the clone into the original and expect it to be found. So, we look for it using the node name.

private static void RemoveNodeByName(string name, XmlNode node)
{
    if (node == null || !node.HasChildNodes) return;
    foreach(XmlNode childNode in node.ChildNodes)
    {
        RemoveNodeByName(name, childNode);
        if (!childNode.Name.Equals(name)) continue;
        node.RemoveChild(childNode);
    }
}

The two methods are rather similar, but the latter one does the actual removing. It loops through every element until it finds the one with the proper name, and then deletes it. When this is done, the looping will stop and we return to the first method. It is not a problem using the name to find the correct XmlNode as the EPiServer XForm does not allow web editors to give multiple fields the same name within the same XForm.

File dumps of the SerializableXmlDocuments

I was curious to get a better look at what kind of XML that the XForm actually generated, so I did a quick save to a file. This is an example of what it looked like before I added my event handler:

<?xml version="1.0"?>
<instance>
    <Fieldname1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="positiveinteger">2</Fieldname1>
    <Fieldname2/>
    <Fieldname3/>
    <Fieldname4>4</Fieldname4>
    <Fieldname5>Mathias</Fieldname5>
    <Fieldname6 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="email">[email protected]</Fieldname6>
</instance>

Afterwards, only the relevant nodes were still there. The attributes are used for keeping track of what kind of value to expect from the field. It always seems to be this two level hierarchy in the XML, so I probably could have just retrieved the instance node by name, and then looped through it’s children; but where is the fun in that?

<?xml version="1.0"?>
<instance>
    <Fieldname1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="positiveinteger">2</Fieldname1>
    <Fieldname4>4</Fieldname4>
    <Fieldname5>Mathias</Fieldname5>
    <Fieldname6 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="email">[email protected]</Fieldname6>
</instance>

When the extra event handler is done removing XmlNodes from the original e.FormData.Data, EPiServer takes over and handles the rest. As the unwanted elements are no longer present in the SaveFormDataEventArgs, they will not be included in the mail.