Thanks to Lemming from AxForum.info for the link:
http://highlight.hohli.com/
The validateWrite method sample in my previous post was formatted via this service.
Friday, August 10, 2012
Thursday, August 9, 2012
ok = ok ? ok : !ok
While working in CUS layer, I keep finding validate* methods that basically look like this:
As you can see, John forgot something: should validateWrittenByJohnDoe return true, the whole validateWrite would succeed, even if both validateSomething and validateSomethingElse failed. That may result in corrupted data.
There is only one case when skipping "&& ok" in the chain of validations is valid - when you explicitly set boolean variable to false:
ok = checkFailed("@CUS1111");
Otherwise, either place an "ok" in front of your check, so that it would be skipped, if the result were false anyway:
ok = ok && this.validateWrittenByJohnDoe();
or place it in the end, so all possible errors will be found and displayed in the infolog:
ok = this.validateWrittenByJohnDoe() && ok;
Keeping all this in mind is especially important in AX 2012, where eventing is possible, as faulty event handlers on validate-method may lead to quite sneaky bugs.
public boolean validateWrite() { boolean ok = true; ok = this.validateSomething() && ok; ok = this.validateSomethingElse() && ok; ... // johndoe 11.11.11 -> ok = this.validateWrittenByJohnDoe(); // johndoe 11.11.11 <- return ok; }
As you can see, John forgot something: should validateWrittenByJohnDoe return true, the whole validateWrite would succeed, even if both validateSomething and validateSomethingElse failed. That may result in corrupted data.
There is only one case when skipping "&& ok" in the chain of validations is valid - when you explicitly set boolean variable to false:
ok = checkFailed("@CUS1111");
Otherwise, either place an "ok" in front of your check, so that it would be skipped, if the result were false anyway:
ok = ok && this.validateWrittenByJohnDoe();
or place it in the end, so all possible errors will be found and displayed in the infolog:
ok = this.validateWrittenByJohnDoe() && ok;
Keeping all this in mind is especially important in AX 2012, where eventing is possible, as faulty event handlers on validate-method may lead to quite sneaky bugs.
Thursday, July 26, 2012
Parm-methods generator script
It may be boring to manualy create parm-methods for each an every class member variable, although there is an editor script for that. By the way, there is a bug-o-feature in AX 2012 editor, which does not allow you to select anything but an EDT for the variable type.
This is a script that auto-generates parm-methods for a class, based on its class declaration:
static void createParmMethod(Args _args)
{
#AOT
ClassName className = classStr(MyClass); // <---------------- Write your class name here
TreeNode classDeclarationTreeNode;
TreeNode classTreeNode;
TreeNode parmMethodNode;
Source classDeclaration;
System.Text.RegularExpressions.MatchCollection mcVariables;
System.Text.RegularExpressions.Match mVariable;
int matchCount;
int matchIdx;
System.Text.RegularExpressions.GroupCollection gcVariableDeclaration;
System.Text.RegularExpressions.Group gVariableDeclarationPart;
str variableType;
str variableName;
str pattern = ' (?<VarType>[a-zA-Z0-9_]+)[ ]+(?<VarName>[a-zA-Z0-9_]+);';
Source parmMethodBody;
classTreeNode = TreeNode::findNode(strFmt(@"%1\%2", #ClassesPath, className));
classDeclarationTreeNode = TreeNode::findNode(
strFmt(@"%1\%2\ClassDeclaration",
#ClassesPath,
className));
classDeclaration = classDeclarationTreeNode.AOTgetSource();
mcVariables = System.Text.RegularExpressions.Regex::Matches(
classDeclaration,
pattern,
System.Text.RegularExpressions.RegexOptions::Singleline);
matchCount = CLRInterop::getAnyTypeForObject(mcVariables.get_Count());
for (matchIdx = 0; matchIdx < matchCount; matchIdx++)
{
mVariable = mcVariables.get_Item(matchIdx);
gcVariableDeclaration = mVariable.get_Groups();
gVariableDeclarationPart = gcVariableDeclaration.get_Item('VarType');
variableType = gVariableDeclarationPart.get_Value();
gVariableDeclarationPart = gcVariableDeclaration.get_Item('VarName');
variableName = gVariableDeclarationPart.get_Value();
parmMethodBody = new xppSource().parmMethod(variableType, variableName);
parmMethodNode = classTreeNode.AOTadd('method1');
parmMethodNode.AOTsetSource(parmMethodBody);
classTreeNode.AOTsave();
}
classTreeNode.AOTcompile();
}
This is a script that auto-generates parm-methods for a class, based on its class declaration:
static void createParmMethod(Args _args)
{
#AOT
ClassName className = classStr(MyClass); // <---------------- Write your class name here
TreeNode classDeclarationTreeNode;
TreeNode classTreeNode;
TreeNode parmMethodNode;
Source classDeclaration;
System.Text.RegularExpressions.MatchCollection mcVariables;
System.Text.RegularExpressions.Match mVariable;
int matchCount;
int matchIdx;
System.Text.RegularExpressions.GroupCollection gcVariableDeclaration;
System.Text.RegularExpressions.Group gVariableDeclarationPart;
str variableType;
str variableName;
str pattern = ' (?<VarType>[a-zA-Z0-9_]+)[ ]+(?<VarName>[a-zA-Z0-9_]+);';
Source parmMethodBody;
classTreeNode = TreeNode::findNode(strFmt(@"%1\%2", #ClassesPath, className));
classDeclarationTreeNode = TreeNode::findNode(
strFmt(@"%1\%2\ClassDeclaration",
#ClassesPath,
className));
classDeclaration = classDeclarationTreeNode.AOTgetSource();
mcVariables = System.Text.RegularExpressions.Regex::Matches(
classDeclaration,
pattern,
System.Text.RegularExpressions.RegexOptions::Singleline);
matchCount = CLRInterop::getAnyTypeForObject(mcVariables.get_Count());
for (matchIdx = 0; matchIdx < matchCount; matchIdx++)
{
mVariable = mcVariables.get_Item(matchIdx);
gcVariableDeclaration = mVariable.get_Groups();
gVariableDeclarationPart = gcVariableDeclaration.get_Item('VarType');
variableType = gVariableDeclarationPart.get_Value();
gVariableDeclarationPart = gcVariableDeclaration.get_Item('VarName');
variableName = gVariableDeclarationPart.get_Value();
parmMethodBody = new xppSource().parmMethod(variableType, variableName);
parmMethodNode = classTreeNode.AOTadd('method1');
parmMethodNode.AOTsetSource(parmMethodBody);
classTreeNode.AOTsave();
}
classTreeNode.AOTcompile();
}
Tuesday, July 24, 2012
Regex looking for return calls within ttsbegin/ttscommit
Return calls should never appear within a ttsbegin/ttscommit pair. If they do, the application may eventually complain that
I have recently had to look for such an issue in a third-party code, and this is the job I used to looks for suspecious "returns", which did find one. Please note, that with the current regex pattern, there may be false positives, but in my case it would take more time to write a perfect pattern, than to manually look through those false positives:
static void checkSourceForReturnsInTTS(Args _args)
{
TreeNode treeNode;
TreeNode sourceTreeNode;
TreeNodeIterator it;
ProjectNode projectNode;
TreeNodeTraverserSource traverser;
Source source;
System.Text.RegularExpressions.MatchCollection mcReturnsInTTS;
int matchCount;
int matchIdx;
str pattern = 'ttsbegin.*[^a-z0-9_]return[^a-z0-9_].*ttscommit';
str matchString;
projectNode = SysTreeNode::getPrivateProject().AOTfindChild("MyProject");
treeNode = projectNode.loadForInspection();
traverser = new TreeNodeTraverserSource(treeNode);
while (traverser.next())
{
sourceTreeNode = traverser.currentNode();
source = sourceTreeNode.AOTgetSource();
source = System.Text.RegularExpressions.Regex::Replace(
source,
'[/][*].*[*][/]',
'',
System.Text.RegularExpressions.RegexOptions::Singleline);
source = System.Text.RegularExpressions.Regex::Replace(
source,
'[/]{2,}.*\n',
'');
mcReturnsInTTS = System.Text.RegularExpressions.Regex::Matches(
strLwr(source),
pattern,
System.Text.RegularExpressions.RegexOptions::Singleline);
matchCount = CLRInterop::getAnyTypeForObject(mcReturnsInTTS.get_Count());
if (matchCount > 0)
{
info(sourceTreeNode.treeNodePath());
}
}
}
I have recently had to look for such an issue in a third-party code, and this is the job I used to looks for suspecious "returns", which did find one. Please note, that with the current regex pattern, there may be false positives, but in my case it would take more time to write a perfect pattern, than to manually look through those false positives:
static void checkSourceForReturnsInTTS(Args _args)
{
TreeNode treeNode;
TreeNode sourceTreeNode;
TreeNodeIterator it;
ProjectNode projectNode;
TreeNodeTraverserSource traverser;
Source source;
System.Text.RegularExpressions.MatchCollection mcReturnsInTTS;
int matchCount;
int matchIdx;
str pattern = 'ttsbegin.*[^a-z0-9_]return[^a-z0-9_].*ttscommit';
str matchString;
projectNode = SysTreeNode::getPrivateProject().AOTfindChild("MyProject");
treeNode = projectNode.loadForInspection();
traverser = new TreeNodeTraverserSource(treeNode);
while (traverser.next())
{
sourceTreeNode = traverser.currentNode();
source = sourceTreeNode.AOTgetSource();
source = System.Text.RegularExpressions.Regex::Replace(
source,
'[/][*].*[*][/]',
'',
System.Text.RegularExpressions.RegexOptions::Singleline);
source = System.Text.RegularExpressions.Regex::Replace(
source,
'[/]{2,}.*\n',
'');
mcReturnsInTTS = System.Text.RegularExpressions.Regex::Matches(
strLwr(source),
pattern,
System.Text.RegularExpressions.RegexOptions::Singleline);
matchCount = CLRInterop::getAnyTypeForObject(mcReturnsInTTS.get_Count());
if (matchCount > 0)
{
info(sourceTreeNode.treeNodePath());
}
}
}
Wednesday, July 18, 2012
The X++ debugger does not open...
... although you turned off "Execute business operations in CIL" in the "Tools > Options".
Then there is probably a runAs(...) method somewhere down the call stack.
You can find such a runAs-call by setting a breakpoint in Visual Studio, attaching to the process and running your logic again. After the runAs-call is found, you can simply go replace it with a direct static method call.
For example:
After this change, the X++ debugger will stop where needed.
Remember - this change is only for debugging purpose. You should not do that in the production code.
Then there is probably a runAs(...) method somewhere down the call stack.
You can find such a runAs-call by setting a breakpoint in Visual Studio, attaching to the process and running your logic again. After the runAs-call is found, you can simply go replace it with a direct static method call.
For example:
After this change, the X++ debugger will stop where needed.
Remember - this change is only for debugging purpose. You should not do that in the production code.
Monday, July 16, 2012
If you forgot to fix DataSource property on a grid,...
... although its child controls are set up properly, then your form may behave in a weird way.
For example, we have a form with 2 linked datasources (SalesTable and SalesLine) and 2 grids accordingly:
The second grid, SalesLineGrid, has DataSource property set to SalesTable. That value was set by default at the moment the grid control was created. You can also see 3 controls in the SalesLineGrid, but their DataSource properties are set to SalesLine, so they are fine.
Now, if we open the form and switch between sales orders back and forth, the bottom grid with sales order lines will not work properly:
As you can see, the cursor position in the second grid changes in sync with that of the first grid, there are a lot of "empty" sales order lines, and, finally, the rendered sales order lines are actually wrong.
Going back to the form in the AOT and setting the SaleLineGrid datasource property to SalesLine will fix the issue:
For example, we have a form with 2 linked datasources (SalesTable and SalesLine) and 2 grids accordingly:
The second grid, SalesLineGrid, has DataSource property set to SalesTable. That value was set by default at the moment the grid control was created. You can also see 3 controls in the SalesLineGrid, but their DataSource properties are set to SalesLine, so they are fine.
Now, if we open the form and switch between sales orders back and forth, the bottom grid with sales order lines will not work properly:
As you can see, the cursor position in the second grid changes in sync with that of the first grid, there are a lot of "empty" sales order lines, and, finally, the rendered sales order lines are actually wrong.
Going back to the form in the AOT and setting the SaleLineGrid datasource property to SalesLine will fix the issue:
Wednesday, July 4, 2012
AIF: Mind auto-generated Axd<DocumentName>.findCorrespondingAxBC method
Problem
You have just created a document service with the wizard. You need the document to contain an unbound value. So, you create a display method on one of the tables, and add a corresponding parm-method to Ax<TableName> class. Then you refresh the services, but the new field is not presented in the schema.
Solution
Check the new Axd<DocumentName> class. By default, it may have findCorrespondingAxBC method, overriding the base class method and always returning classnum(AxCommon). This is why your new parm-method in the Ax<TableName> class is ignored.
P. S. There are actually TODOs added to the auto-generated methods. It may be a good idea to clean up the TODOs first, and then continue with the service development.
You have just created a document service with the wizard. You need the document to contain an unbound value. So, you create a display method on one of the tables, and add a corresponding parm-method to Ax<TableName> class. Then you refresh the services, but the new field is not presented in the schema.
Solution
Check the new Axd<DocumentName> class. By default, it may have findCorrespondingAxBC method, overriding the base class method and always returning classnum(AxCommon). This is why your new parm-method in the Ax<TableName> class is ignored.
P. S. There are actually TODOs added to the auto-generated methods. It may be a good idea to clean up the TODOs first, and then continue with the service development.
Subscribe to:
Posts (Atom)




