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:
<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