Importing and exporting valid data using XMLports in Dynamics 365 Business Central

Applies to: Dynamics 365 Business Central

In Dynamics 365 Business Central, you can use XMLports to export and import data between an external source and Business Central. If you’re not familiar with the technology, you can learn more by reading XMLport Object.

In this blog post, we’ll take a closer look at how to make sure that the XML that is being imported is valid.

Before we get into details about XMLports, here’s a quick reminder about default values:

  • Default values for the MinOccurs and MaxOccurs properties of an element are Once and Unbounded, respectively.
  • Default value for the Occurrence property of an attribute is Required.

XML validation

Basically there are two validations that XML is validated against:

  • A generated XSD (XML Schema Definition).
  • A created XMLport definition.

To better understand the impact of those two validations, let’s take a look at a few examples.

Imagine that you have an XMLport where the elements Amount and Date have the MinOccurs property set to Zero and they belong to different nodes that share the same XML name.

xmlport 50101 DiffComplexZeroOccurs
{
    Encoding = UTF8;
    Namespaces = FIN = 'Finance';

    schema
    {
        textelement(Invoice)
        {
            textelement(Tax)
            {
                NamespacePrefix = 'FIN';

                textelement(Amount)
                {
                    NamespacePrefix = 'FIN';
                    MinOccurs = Zero;
                }
            }

            textelement(Total)
            {
                textelement(Tax2)
                {
                    XmlName = 'Tax';
                    NamespacePrefix = 'FIN';

                    textelement(Date)
                    {
                        NamespacePrefix = 'FIN';
                        MinOccurs = Zero;
                    }
                }
            }
        }
    }
}

 

Validation against a generated XSD

When creating XMLports, you need to make sure that the first element that is referenced outside its namespace is the most general (we will call this rule “First General”). Based on that element, an element in XSD is created and that XSD (generated schema) is used later to validate the XML.

If you try to import XML that has Tax -> Amount and Tax -> Date (XML50101_DiffComplexZeroOccurs_FailSchema.xml), you will get an error because the XML is not valid according to a generated XSD. A type in XSD is created based on the first element referenced outside its namespace, meaning that the first Tax element is a child of the element Invoice, which does not have any prefix, unlike the element Tax, which is part of the Finance namespace. As you can see from the first Tax element’s definition, it only has one child Amount. We get an error in this scenario because Invoice and Total are referring to the same element Tax, but the definition is not compatible because there is no information about a possible child Date.

[XML50101_DiffComplexZeroOccurs_FailSchema.xml]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Invoice xmlns:FIN="Finance">
  <FIN:Tax>
    <FIN:Amount />
  </FIN:Tax>
  <Total>
    <FIN:Tax>
      <FIN:Date />
    </FIN:Tax>
  </Total>
</Invoice>

Shows an error message about XML not being valid.

Validation against a created XMLport

If you have the same XMLport but this time the XML contains exactly the same Tax -> Amount elements (XML50101_DiffComplexZeroOccurs_FailMetadata.xml), it will pass the schema validation but it will fail because the XMLport definition in XMLport is defined so that the second Tax element should have a child Date.

[XML50101_DiffComplexZeroOccurs_FailMetadata.xml]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Invoice xmlns:FIN="Finance">
  <FIN:Tax>
    <FIN:Amount />
  </FIN:Tax>
  <Total>
    <FIN:Tax>
      <FIN:Amount />
    </FIN:Tax>
  </Total>
</Invoice>

Shows error message that the Amount element is not expected.

“First General” rule

So, what should your XMLport look like to make the first XML document (XML50101_DiffComplexZeroOccurs_FailSchema.xml) pass?

You must make the first referenced element (in this case the first Tax element) the most general so that all referenced Tax elements match the same definition. For example, you can add an optional element Date to the first Tax element.

Note: These validations also check if elements in XML have the correct occurrence, which is set in the MinOccurs and MaxOccurs properties.

xmlport 50102 DiffComplexZeroOccursGeneral
{
    Encoding = UTF8;
    Namespaces = FIN = 'Finance';

    schema
    {
        textelement(Invoice)
        {
            textelement(Tax)
            {
                NamespacePrefix = 'FIN';

                textelement(Amount)
                {
                    NamespacePrefix = 'FIN';
                    MinOccurs = Zero;
                }

                textelement(DateOpt)
                {
                    XmlName = 'Date';
                    NamespacePrefix = 'FIN';
                    MinOccurs = Zero;
                }
            }

            textelement(Total)
            {
                textelement(Tax2)
                {
                    XmlName = 'Tax';
                    NamespacePrefix = 'FIN';

                    textelement(Date)
                    {
                        NamespacePrefix = 'FIN';
                        MinOccurs = Zero;
                    }
                }
            }
        }
    }
}

The rule “First General” only applies in cases where an element is referenced outside its namespace (in this case the element Tax). As you can see, the elements Invoice and Total do not belong to namespace Finance. If the elements Total and Invoice have the same prefix Finance, then the behavior would be the same as having a no namespace scenario.

If all the elements are under the same prefix Finance (xmlport 50103 DiffComplexAllPrefix), then there are no problems importing an XML document with Tax -> Amount and Tax -> Date if you append FIN to the beginning of elements Invoice and Total. This is due to a new type creation for each element when they are referenced internally.

xmlport 50103 DiffComplexAllPrefix
{
    Encoding = UTF8;
    Namespaces = FIN = 'Finance';

    schema
    {
        textelement(Invoice)
        {
            NamespacePrefix = 'FIN';

            textelement(Tax)
            {
                NamespacePrefix = 'FIN';

                textelement(Amount)
                {
                    NamespacePrefix = 'FIN';
                    MinOccurs = Zero;
                }
            }

            textelement(Total)
            {
                NamespacePrefix = 'FIN';

                textelement(Tax2)
                {
                    XmlName = 'Tax';
                    NamespacePrefix = 'FIN';

                    textelement(Date)
                    {
                        NamespacePrefix = 'FIN';
                        MinOccurs = Zero;
                    }
                }
            }
        }
    }
}
[XML50103_DiffComplexAllPrefix.xml]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<FIN:Invoice xmlns:FIN="Finance">
  <FIN:Tax>
    <FIN:Amount />
  </FIN:Tax>
  <FIN:Total>
    <FIN:Tax>
      <FIN:Date />
    </FIN:Tax>
  </FIN:Total>
</FIN:Invoice>

The rule “First General” is responsible for defining an element’s children. Attributes can be defined in different places and all of them will be combined as long as they are compatible (see the Attributes section for more details).

Attributes

Imagine that you have an XMLport where there are 3 elements named Amount that belong to the same namespace Finance and are referenced outside it. Also, these elements have a different number of attributes but all of them have the Occurrence property set to Optional.

xmlport 50104 PrefixElemWithOptAttr
{
    Encoding = UTF8;
    Namespaces = FIN = 'Finance';

    schema
    {
        textelement(Invoice)
        {
            textelement(Payment)
            {
                textelement(Amount1)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';
                }
            }

            textelement(Total)
            {
                textelement(Amount2)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(Currency)
                    {
                        Occurrence = Optional;
                    }
                }
            }

            textelement(Order)
            {
                textelement(Amount3)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(ID)
                    {
                        Occurrence = Optional;
                    }

                    textattribute(Name)
                    {
                        Occurrence = Optional;
                    }
                }
            }
        }
    }
}

Remember that the first element referenced outside its namespace is used to create a type in XSD, which is used later while validating XML. The scenario with attributes is a bit different. All the attributes are combined for the same elements referenced outside their namespace as long as they are compatible. Compatibility means that if there is a required attribute, it needs to be defined in all the referenced elements. An optional attribute can be defined in only one place and this will not break the XSD validation.

Order of attributes

The good thing about attributes is that their positioning does not matter. For example, in the first XML document (XML50104_PrefixElemWithOptAttr.xml), the last element Amount has two attributes, ID and Name, while in the second document (XML50104_PrefixElemWithOptAttr_RandomOrder.xml) the position of those two attributes is swapped. This will not cause any issues and both documents are imported successfully.

[XML50104_PrefixElemWithOptAttr.xml]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Invoice xmlns:FIN="Finance">
  <Payment>
    <FIN:Amount />
  </Payment>
  <Total>
    <FIN:Amount Currency="" />
  </Total>
  <Order>
    <FIN:Amount ID="" Name="" />
  </Order>
</Invoice>
[XML50104_PrefixElemWithOptAttr_RandomOrder.xml]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Invoice xmlns:FIN="Finance">
  <Payment>
    <FIN:Amount />
  </Payment>
  <Total>
    <FIN:Amount Currency="" />
  </Total>
  <Order>
    <FIN:Amount Name="" ID="" />
  </Order>
</Invoice>

Validation against a created XMLport

XML is validated against a created XMLport definition. If you try to import the following XML, it will fail because the element Amount, which is the child of Total, only has one attribute Currency and there is no information about the attribute ID.

[XML50104_PrefixElemWithOptAttr_FailMetadata.xml]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Invoice xmlns:FIN="Finance">
  <Payment>
    <FIN:Amount />
  </Payment>
  <Total>
    <FIN:Amount Currency="" ID="" />
  </Total>
  <Order>
    <FIN:Amount ID="" Name="" />
  </Order>
</Invoice>

Shows error message that the ID attribute is not expected.

Consistency of attributes

Let’s look at another XMLport example that has a different definition for the element Amount, which is a child of the element Order.

xmlport 50105 PrefixElemWithMixAttr
{
    Encoding = UTF8;
    Namespaces = FIN = 'Finance';

    schema
    {
        textelement(Invoice)
        {
            textelement(Payment)
            {
                textelement(Amount1)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';
                }
            }

            textelement(Total)
            {
                textelement(Amount2)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(Currency)
                    {
                        Occurrence = Optional;
                    }
                }
            }

            textelement(Order)
            {
                textelement(Amount3)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(ID) { }
                }
            }
        }
    }
}

Trying to import any XML, such as XML50105_PrefixElemWithMixAttr_FailSchema.xml, will result in an error because there are conflicts between the element’s Amount attributes. The attribute ID is Required by default, but other Amount elements do not have a definition.

[XML50105_PrefixElemWithMixAttr_FailSchema.xml]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Invoice xmlns:FIN="Finance">
  <Payment>
    <FIN:Amount />
  </Payment>
  <Total>
    <FIN:Amount Currency="" />
  </Total>
  <Order>
    <FIN:Amount ID="" />
  </Order>
</Invoice>

Shows error that the XML schema is not valid.

Resolving a consistency error

To fix the error “…The ‘{0}’ attribute is not defined consistently on the ‘{1}’ element…”, do one of the following:

  • Add the attribute to all ‘{1}’ elements and set its Occurrence property to Required.
  • For ‘{1}’ elements that require the attribute, rename the element.
  • For ‘{1}’ elements that have the attribute’s Occurrence property set to Required, change this property to Optional or delete the attribute.

The first solution is to define the attribute ID in all the Amount elements that are referenced outside their namespace.

xmlport 50106 PrefixElemWithMixAttrReq
{
    Encoding = UTF8;
    Namespaces = FIN = 'Finance';

    schema
    {
        textelement(Invoice)
        {
            textelement(Payment)
            {
                textelement(Amount1)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(ID1)
                    {
                        XmlName = 'ID';
                    }
                }
            }

            textelement(Total)
            {
                textelement(Amount2)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(Currency)
                    {
                        Occurrence = Optional;
                    }

                    textattribute(ID2)
                    {
                        XmlName = 'ID';
                    }
                }
            }

            textelement(Order)
            {
                textelement(Amount3)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(ID) { }
                }
            }
        }
    }
}

The impact of this change is that XML50105_PrefixElemWithMixAttr_FailSchema.xml needs to be updated, so that all elements named Amount must have an attribute ID.

xmlport 50107 PrefixElemWithMixAttrRename
{
    Encoding = UTF8;
    Namespaces = FIN = 'Finance';

    schema
    {
        textelement(Invoice)
        {
            textelement(Payment)
            {
                textelement(Amount1)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';
                }
            }

            textelement(Total)
            {
                textelement(Amount2)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(Currency)
                    {
                        Occurrence = Optional;
                    }
                }
            }

            textelement(Order)
            {
                textelement(AmountRenamed)
                {
                    NamespacePrefix = 'FIN';

                    textattribute(ID) { }
                }
            }
        }
    }
}

There is a slight change in XML50105_PrefixElemWithMixAttr_FailSchema.xml, because the element Amount has to be renamed, for example AmountRenamed.

[XML50107_PrefixElemWithMixAttrRename.xml]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Invoice xmlns:FIN="Finance">
  <Payment>
    <FIN:Amount />
  </Payment>
  <Total>
    <FIN:Amount Currency="" />
  </Total>
  <Order>
    <FIN:AmountRenamed ID="" />
  </Order>
</Invoice>

The final solution is to make the attribute ID optional.

xmlport 50108 PrefixElemWithMixAttrOpt
{
    Encoding = UTF8;
    Namespaces = FIN = 'Finance';

    schema
    {
        textelement(Invoice)
        {
            textelement(Payment)
            {
                textelement(Amount1)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';
                }
            }

            textelement(Total)
            {
                textelement(Amount2)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(Currency)
                    {
                        Occurrence = Optional;
                    }
                }
            }

            textelement(Order)
            {
                textelement(Amount3)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(ID)
                    {
                        Occurrence = Optional;
                    }
                }
            }
        }
    }
}

If there is no change in XML50105_PrefixElemWithMixAttr_FailSchema.xml there is no protection that the last element Amount has the attribute ID.

Note that if there are no elements where Amount is referenced outside its namespace, meaning that the elements Payment, Total, and Order belong to the same Finance namespace, then a new type for each element Amount is created. In this case, the definitions of the element Amount can be different because they will end up having different types. Let’s take a look at that in this XMLport.

xmlport 50109 PrefixElemWithMixAttrNamespace
{
    Encoding = UTF8;
    Namespaces = FIN = 'Finance';

    schema
    {
        textelement(Invoice)
        {
            textelement(Payment)
            {
                NamespacePrefix = 'FIN';

                textelement(Amount1)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';
                }
            }

            textelement(Total)
            {
                NamespacePrefix = 'FIN';

                textelement(Amount2)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(Currency)
                    {
                        Occurrence = Optional;
                    }
                }
            }

            textelement(Order)
            {
                NamespacePrefix = 'FIN';

                textelement(Amount3)
                {
                    XmlName = 'Amount';
                    NamespacePrefix = 'FIN';

                    textattribute(ID) { }
                }
            }
        }
    }
}

[XML50109_PrefixElemWithMixAttrNamespace.xml]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Invoice xmlns:FIN="Finance">
  <FIN:Payment>
    <FIN:Amount />
  </FIN:Payment>
  <FIN:Total>
    <FIN:Amount Currency="" />
  </FIN:Total>
  <FIN:Order>
    <FIN:Amount I="" />
  </FIN:Order>
</Invoice>

Author: Viktorija Nekrasaite, Software Engineer with the Dynamics 365 Business Central team

Blog post updated on May 27, 2019, with additional code snippets for better illustration of my points.