Tuesday 12 May 2015

Update DefaultDimension through Connector for Microsoft Dynamics

We had interesting problem with updating of some data entities' DefaultDimension. Normally for update Connector asks for original data (performs find operation), uses those data as default data and overwrite only those data where values are provided from 3rd party system. It means that any of data defaulted in AX, when create operation was performed, is not overwritten by empty values.



This behaviour is different for dictionaries (DefaultDimension is dictionary from WCF service perspective). Connector takes new dictionary of dimensions provided from 3rd party system and then adds original dimension provided from AX.

Result looks like:

<DefaultDimension>
<Values xmlns="http://schemas.microsoft.com/dynamics/2008/01/sharedtypes">
<Value>
<Name>Department</Name>
<Value>00000023</Value>
</Value>
<Value>
<Name>CostCenter</Name>
<Value>00000022</Value>
</Value>
<Value>
<Name>CostCenter</Name>
<Value>00000001</Value>
</Value>
<Value>
<Name>Department</Name>
<Value>00000002</Value>
</Value>
</Values>
</DefaultDimension>


But expected XML looks like:

<DefaultDimension>
<Values xmlns="http://schemas.microsoft.com/dynamics/2008/01/sharedtypes">
<Value>
<Name>Department</Name>
<Value>00000023</Value>
</Value>
<Value>
<Name>CostCenter</Name>
<Value>00000022</Value>
</Value>
</Values>
</DefaultDimension>

Result of the first XML is that all except last nodes are ignored. So Dimensions are being updated to values 00000001 and 00000002 instead of 00000023 and 00000022.

How to solve it?
1) Ask Microsoft to solve this bug in Connector. 
2) Create workaround with XSLT to remove duplicated dimensions.

We tried option 1 as a Microsoft's partner. Response? We do not support modifications you did. Sorry.

So we had to go through option 2.

Solution is based on pipeline operations. We could not use out-of-box "Transform all request" option on inbound port as there are more services on the port and we want to transform couple of them only.

We've specified that class DocServicesCreateOp will be called as pipeline operations. (This setting is available on Inbound port.)
DocServicesCreateOp has method execute which is being executed by AIF. 

public void execute(AifPipelineComponentRecId componentRecId,
    AifMessage message, AifPipelineParms parms)
{
    str                             xml;
    AifPipelineOperation            pipelineOp;

    // Make sure component rec ID is not zero
    if (componentRecId == 0)
        // Value cannot be 0.  Parameter name: %1
        throw error(strFmt("@SYS93168", varStr(componentRecId)));

    // Make sure parameters are not null
    if (! message)
        // Value cannot be null.  Parameter name: %1
        throw error(strFmt("@SYS91439", varStr(message)));
    if (! parms)
        // Value cannot be null.  Parameter name: %1
        throw error(strFmt("@SYS91439", varStr(parms)));
    
    xml = message.getXml();
    pipelineOp.parmXml(xml);
    pipelineOp.execute();
    xml = pipelineBase.parmXml();
    message.setXml(xml);
}

We are calling specific pipeline operation - AifPipelineOperation.execute.

class AifPipelineOperation
{
    XML                             xml;
}

public XML parmXml(XML _xml = xml)
{
    xml = _xml;

    return xml;
}

public void execute()
{
    //transform xml - remove duplicates from default dimensions
    this.transformData();
}

parmXslt method provides XSLT file content from AOT Resources node:

public AifTransformXslContent parmXslt()
{
    resourceNode node;
    BinData binData;

    //get resource with xslt
    node = SysResource::getResourceNode('PipelineTransformationXslt');

    //read xslt
    binData = new BinData();
    binData.setData(SysResource::getResourceNodeData(node));

    xslt = binData.getStrData();

    //remove BOM
    if(subStr(xslt, 1, 1) != "<")
    {
        xslt = subStr(xslt, 4, strLen(xslt) - 3);
    }

    return xslt;
}

And this is transformData method:

public void transformData()
{
    System.Xml.Xsl.XslCompiledTransform transform;
    System.Text.StringBuilder           stringBuilder;
    System.Xml.Xsl.XsltSettings         xsltSettings;
    System.Xml.XmlWriter                outputWriter;
    ;

    try
    {
        //set XSLT params
        xsltSettings = new System.Xml.Xsl.XsltSettings();
        xsltSettings.set_EnableScript(true);

        //Local XSLT
        //xslt is set in specific classes
        transform = new System.Xml.Xsl.XslCompiledTransform();
        transform.Load(System.Xml.XmlReader::Create(new System.IO.StringReader(this.parmXslt())),
                            xsltSettings,
                            new System.Xml.XmlUrlResolver());

        //transform the input AIF XML
        stringBuilder = new System.Text.StringBuilder();
        outputWriter = System.Xml.XmlWriter::Create(stringBuilder, transform.get_OutputSettings());
        transform.Transform(System.Xml.XmlReader::Create(new System.IO.StringReader(xml)), outputWriter);

        //return transformated XML
        xml = stringBuilder.ToString();
    }
    catch (Exception::CLRError)
    {
        outputWriter.Close();
        throw Global::error(CLRInterop::getLastException().toString());
    }
}

Finally example of XSLT file:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:req="https://www.yourdomain.com/yourport/yourservice"
xmlns:fs="http://schemas.microsoft.com/dynamics/2008/01/documents/YourDocument"
xmlns:val="http://schemas.microsoft.com/dynamics/2008/01/sharedtypes">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="kDimName" match="/req:YourServiceUpdateRequest/fs:YourDocument/fs:YourTable/fs:DefaultDimension/val:Values/val:Value" use="val:Name"/>

<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>

<xsl:template match="/req:YourServiceUpdateRequest/fs:YourDocument/fs:YourTable/fs:DefaultDimension/val:Values/val:Value[not(generate-id() = generate-id(key('kDimName', val:Name)[1]))]"/>
</xsl:stylesheet>

Note: Don't forget to update namespaces and Xpath for your service.

No comments:

Post a Comment