X++ Coding StandardsGeneral principles are: Declare variables as
locally as possible. Check the error conditions in the beginning;
return/abort as early as possible. Have only one successful return
point in the code (typically, the last statement), with the
exception of switch cases, or when checking for start conditions.
Keep the building blocks (methods) small and clear. A method should
do a single, well-defined job. It should therefore be easy to name
a method. Put braces around every block of statements, even if
there is only one statement in the block. Put comments in your
code, telling others what the code is supposed to do, and what the
parameters are used for. Do not assign values to, or manipulate,
actual parameters that are "supplied" by value. You should always
be able to trust that the value of such a parameter is the one
initially supplied. Treat such parameters as constants. Clean up
your code; delete unused variables, methods and classes. Never let
the user experience a runtime error. Take appropriate actions to
either manage the situation programmatically or throw an error
informing the user in the Infolog about the problem and what
actions can be taken to fix the problem. Never make assignments to
the "this" variable. Avoid dead code. (See Dead Code Examples.)
Reuse code. Avoid using the same lines of code in numerous places.
Consider moving them to a method instead. Never use infolog.add
directly. Use the indirection methods: error, warning, info and
checkFailed. Design your application to avoid deadlocks. More
specific code standards are described below: X++ layout Comments
Semicolons Constants Arrays Dates try/catch statements throw
statements ttsBegin and ttsCommit if ... else and switch statements
select StatementsX++ LayoutGeneral Guidelines
Only one statement per line. Break up complex expressions that
are more than one line - make it visually clear. Use a single blank
line to separate entities. Do not use parentheses around the case
constants. Do not use parentheses around where expressions. Add one
space between if, switch, for, while and the expressions starting
parentheses. For example: if (creditNote) Use braces around all
code blocks, except for around case clauses in a switch statement.
Use braces even if there is only one statement in the block.
Indentation
An indentation is equivalent to four (4) characters, which
corresponds to one tab in the X++ editor. You must not start a new
line in columns 2, 3 or 4. Put opening and closing braces, { and },
on the same level, on separate lines, and aligned with the command
creating the block. They must be at the start of a line, and in a
tab column position (1, 5, 9 etc.). Braces must be on a dedicated
line unless a opening brace is followed by a semicolon ( {; ) or a
closing brace is followed by a while ( }while ). The following
reserved words should be placed at the beginning of a line: case,
catch, changeCompany, continue, default, else, for, if, retry,
return, switch, try, ttsAbort, ttsBegin, ttsCommit, while. The
exceptions to this rule are: case: (reserved words in a case
statement) default: (reserved words in a default statement) else
if}while If a line of code starts with any other reserved word, or
with an alphabetical character, the line should start in a tab
column position (1, 5, 9 etc). The following reserved words must be
placed in a tab column position: case, catch, changeCompany,
continue, default, else, for, if, retry, return, switch, try,
ttsAbort, ttsBegin, ttsCommit, while. The exceptions to these rules
are: case: (reserved words in a case statement) default: (reserved
words in a default statement) else if}while The reserved word else
must have a dedicated line unless you write else if. switch-case
statements: indent case and default statements by 1 level (with any
code within these indented a further level) and indent break
statements by two levels. Indent where and other qualifiers to the
select statement by one level. If Booleans are used in a long
expression, put them at the start of an indented new line. Example
switch-case StatementCopyswitch (myEnum){ case ABC::A: ... break;
case ABC::B ... break; default: ... break;}Example select
StatementCopyselect myTable index hint myIndex where myTable.field1
== 1 && myTable.field2 == 2;Example Layout of Booleans in a
Long ExpressionCopyselect firstOnly utilElements where
utilElements.recordType == recordType &&
utilElements.parentId == parentID && utilElements.name ==
elementName;Column Layout
Column layout should be used where more than one line has the
same structure; for example, in declarations or assignments. Do not
use column layout when there is only one row, or if consecutive
rows do not have the same structure. Column format is defined using
extra blanks. Example Column LayoutCopytmpABC.refRecId =
inventTable.recId;tmpABC.itemGroupId =
inventTable.itemGroupId;tmpABC.itemId =
inventTable.itemId;tmpABC.amount = amount;tmpABC.oldValue =
this.getCategory(inventTable);tmpABC write();Layout for Methods
The starting parenthesis on method declarations and calls should
be the character just after the method name (no space). If there
are one or two parameters, the parameters can be listed on the same
line. If there are more than two parameters, move each parameter
onto a new line, and indent by 4 spaces. Example Layout for Method
with One or Two ParametersCopy
myMethod(parameter1, parameter2);Example Layout for Method with
Many ParametersCopymyMethod( parameter1, parameter2,
parameter3);X++ Standards: CommentsUse // for both single and
multiline (block) comments. There should be a space between the
"//" and the start of the comment. Comments should be in US-English
and start with an uppercase letter (unless the first word is a
lowercase name). Put comments on a separate line before the code
they are describing. The only exception to this is when you are
describing parameters. In this case, put one parameter per line,
with the comment describing it to the right of the parameter. When
creating a multiline comment, do not write on the first or the last
line of the comment (as shown in the following example). For
example: // Single line comment
//// Comment on multiple lines.// Do not add text to 1st or last
line of comment.//
Comments should not include: Dates Names Aliases Version or
layer references Bug numbers unless it is a workaround, or unless
the code could appear inappropriate if you didn't know that it was
for a bug fix. Politically or culturally sensitive phrases Note
If you put a comment at the start of the method to describe its
purpose and use, you can use block comments (/* */).
Remove Commented-out Code from the Application
The Best Practice is that we don't ship a product with
commented-out code, so any commented-out code should be removed.
Tip
To find comments in the source (both // .. and /* .. */), use
the Find dialog to search for methods containing the text (regular
expression): /[/\*]
X++ Standards: Using SemicolonsAlways place a semicolon (;) on
an empty line in front of the first statement in your code (after
the variable declarations). This is particularly important if the
statement does not begin with a keyword (select, while, and so on).
For example, the first part of the statement is a variable or a
type reference. You should use a semicolon even if the code
compiles. Types introduced later (that have the same name as the
first part of the statement) might prevent the code from compiling.
X++ Standards: ConstantsFollow the best practices rules about using
constants. These are designed to make it easier to maintain X++
code. Constants
Rule Error level
Do not use hard-coded constants (except 0, 1, 100). Warning
Define constants in a method, class declaration, or if necessary
globally in a macro. Reuse existing constants. Consider alternative
ways of getting the constant: Intrinsic Functions maxInt, minInt,
funcName, maxDate system functions Global macros None
User Interface Text
Rule Error level
User interface text must be in double quotes, and you must
always use a label (also in double quotes). Error
User interface labels must be complete sentences. Do not build
sentences using more than one label, or other constants or
variables under program control (do not use concatenation).
Example: Description description = "@SYS12345"Use strFmt to format
user interface text. None
System-oriented Text
Rule Error level
System-oriented text constants must be in single quotes.
None
Do not hard-code text. Warning
Do not use labels. You will get a warning if a label is used
inside single quotes.Example:
Copy#define.Filename('myFile.txt')Filename filename =
#Filename;Warning
Numeric Constants
Rule Error level
Always review the direct use of numeric constants, except for 0
meaning null, 1 meaning increment, and 100 when calculating
percents and currencies. None
Certain numeric constants are predefined, such as the number of
days per week, and the number of hours per day. For example, see
the TimeConstants and SysBitPos macros in the Application Object
Tree (AOT). None
X++ Standards: ArraysThis topic describes the best practice for
using the memory option in arrays. For more information, see
Arrays. The memory option is the optional second array declaration
option. It specifies how many consecutive entries in the array will
be held in memory at a particular time. The rest will reside on
disk (a cached temporary file, indexed by the array index). Dynamic
arrays are sized according to the maximum index used. Tip
Use the memory option to limit the amount of RAM used to hold
the data of the array when you work with high numbered indexes (and
large cells).
If you use all (or nearly all) of the entries in an array, set
the memory option to a large number, or do not set it at all. If
you only use a few of the entries in the array, set the memory
option to a small number, such as 1. Read Performance
If you consider using the memory option on an array where you
use all (or almost all) of the entries, the look up performance
should be considered. If you traverse an array sequentially, such
as with an index of 1, 2, 3, ..., n, you will probably not
experience any read performance problems. The cell data blocks will
be read sequentially from disk and they will be read to the end
before the next block is read (disk reads will be number of
entries/memory option size). If you traverse an array randomly
(such as with an index of 300, 20, 5, 250, n, ..., 50) the cell
data will also be read from disk randomly, so you may experience
read performance problems (disk reads could be as high as the
number of entries). Example 1MyTable myTable;boolean
foundRecord[,1];;while select myTablewhere myTable ...
{foundRecord[myTable.recId] = true;...}Example 2CustTable
custTable;CustAccount foundAccount[];int i;;while select
custTablewhere custTable... {i++;foundAccount[i] =
custTable.AccountNum;...}Example 3Name foundName[,100];int i;;while
select custTablewhere custTable... {i++;foundName[i] =
custTable.Name;}X++ Standards: DatesWhen you are programming with
dates, Best Practices are: Use only strongly typed (date) fields,
variables, and controls (do not use str or int). Use Auto settings
in date formatting properties. Use DateTimeUtil::getSystemDateTime
instead of systemDateGet or today. The today function uses the date
of the machine. The systemDateGet method uses the system date in
Microsoft Dynamics AX. Only DateTimeUtil::getSystemDateTime
compensates for the time zone of the user. Avoid using date2str for
performing date conversions. Use Strong Typing (date)
Never permanently store a date in anything other than a date
field. Always present a date in a date control. Fields, variables,
and controls should be defined by extended data types. These should
have their formatting properties set to Auto so that you do not
take control away from the users' specific date formatting setup.
Current Business Date
Most application logic should use the system function
systemDateGet, which holds the logic business date of the system
(this can be set from the status bar). The system function today()
should be used only where the actual machine date is needed. This
is seldom the case. Note
The date and time can be different on the client and the
server.
Avoid String / Date Conversions
You will not typically need to format a date to a string. Use
date fields, variables, and date controls on forms and reports
instead. If you need to format a date to a string: For user
interface situations, use strFmt or date2Str with -1 in all the
formatting parameters. This ensures that the date is formatted in
the way that the user has specified in Regional Settings. For other
specific system-related situations, such as communication with
external systems, use date2Str. When you let Regional Settings
dictate the format, be aware that it can change from user to user
and might not be a suitable format for external communication.
Using str2Date indicates that dates are being used that have had a
string format. X++ Standards: try/catch StatementsAlways create a
try/catch deadlock/retry loop around database transactions that
might lead to deadlocks. Whenever you have a retry, all the
transient variables must be set back to the value they had just
before the try. The persistent variables (that is, the database and
the Infolog) are set back automatically by the throw that leads to
the catch/retry. Example
Copytry{ this.createJournal(); this.printPosted();}catch
(Exception::Deadlock){ this.removeJournalFromList(); retry;}X++
Standards: throw StatementsThe throw statement automatically
initiates a ttsAbort, which is a database transaction rollback. The
throw statement should be used only if a piece of code cannot do
what it is expected to do. The throw statement should not be used
for more ordinary program flow control. Always place an explanation
of the throw in the Infolog before the actual throw. Note
Do not use ttsAbort directly; use throw instead.
X++ Standards: ttsBegin and ttsCommitttsBegin and ttsCommit must
always be used in a clear and well-balanced manner. Balanced
ttsBegin and ttsCommit statements are the following: Always in the
same method. Always on the same level in the code. Avoid making
only one of them conditional. Use throw, if a transaction cannot be
completed. Do not use ttsAbort; use throw instead. Do not use
anything that requires a user interaction within a transaction
(such as an action on a dialog box). X++ Standards: if ... else and
switch StatementsThis topic describes X++ code style standards for
the if...else statement and the switch statement. if...else
If you have an if...else construction, then use positive logic:
Preferred: if (true) {... } else {... }Avoid: if (!false){... }
else {... }It is acceptable to use negative logic if throwing an
error, and in cases where the use of positive logic would make the
code difficult to understand. There should be a space character
between the if keyword and the open parenthesis. Switch
Statements
Always end a case with a break statement (or return/throw). If
you intentionally want to make use of the fall-through mechanism
supported in X++, replace the missing break statement with a
comment line: // Fall throughThis comment line makes it visually
clear to the reader that the fall-through mechanism is utilized.
Use 3 levels of indentation: switch (Expression){case:
Constant:Statement;break;...}Do not put parentheses around cases.
There should not be a space between the case keyword and the colon
character. Use a switch Instead of Nested if ... else
Statements
Use switch statements instead of nested if...else statements.
Recommended: switch (myEnum){case ABC::A:...break;case
ABC::B:...break;case ABC::C:...break;default:...break;}Avoid: if
(myEnum == ABC::A){...}else{if (myEnum == ABC::B){...}else{if
(myEnum == ABC::C){...}else{...}}}Switch StatementsThe switch
statement is a multi-branch language construct. You can create more
than two branches by using the switch statement. This is in
contrast to the if statement. You have to nest statements to create
the same effect. The general format of a switch statement is as
follows. switch (Expression){case
Constant:Statement;break;...default:Statement;break;}The switch
expression is evaluated and checked against each of the case
compile-time constants. If a constant matches the switch
expression, the case statement is executed. If the case also
contains a break statement, the program then jumps out of the
switch. If there is no break statement, the program continues
evaluating the other case statements. If no matches are found, the
default statement is executed. If there are no matches and no
default, none of the statements inside the switch are executed.
Each of the previous Statement lines can be replaced with a block
of statements by enclosing the block in {...} braces. Syntax
Switch statement = switch( expression) { {case } [ default:
statement ] }case = case expression { , expression } :
statementExamples
switch (Debtor.AccountNo){case "1000" : do_something;break;case
"2000" : do_something_else;break;default :default_statement;
break;}It is possible to make the execution drop through case
branches by omitting a break statement. For example: switch
(x){case 10:a = b;case 11:c = d;break;case 12:e = f;break;}Here, if
x is 10, b is assigned to a, and d is assigned to c, the break
statement is omitted after the case 10: statement. If x is 11, d is
assigned to c. If x is 12, f is assigned to e. Ternary Operator
(?)In the X++ language of Microsoft Dynamics AX, the ternary
operator is a conditional statement that resolves to one of two
expressions. This means that a ternary operation statement can be
assigned to a variable. In comparison, an if statement provides
conditional branching of program flow but cannot be assigned to a
variable. Syntax
expression1 ? expression2 : expression3expression1 must be a
Boolean expression. If expression1 is true, the whole ternary
statement resolves to expression2; otherwise it resolves to
expression3. expression2 and expression3 must be of the same type
as each other. Example 1
This section describes a code example that returns one of two
strings based on a Boolean return value from a method call. The
Boolean expression indicates whether the CustTable table has a row
with a RecId field value of 1. result =
(custTable::find("1").RecId) ? "found" : "not found";If this
Boolean expression is true (meaning RecId != 0), found is assigned
to result. Otherwise, the alternative not found is assigned to
result. Example 2
This section describes a code example that has one ternary
statement nested inside another ternary statement. Copyprint(
(custTable.AccountNum > "1000") ? ( (custTable.AccountNum <
"2000") ? "In interval" : "Above 2000" ) : "low" );If AccountNum is
not greater than 1000, the expression is equal to the third
expression and low is printed. If AccountNum is greater than 1000,
the second expression is evaluated, and this also contains a
ternary operator. If AccountNum is greater than 1000 and less than
2000, In interval is printed. If AccountNum is greater than 1000
and greater than or equal to 2000, Above 2000 is printed.
Comparison to the IF Statement
This section describes a comparison of the ternary statement to
the if statement. Copya = (b > c) ? b : c;
// The preceding line of code is equivalent to the following
if-else code:
if (b > c){ a = b;}else{ a = c;}Transaction
IntegrityMicrosoft Dynamics AX has two internal checking features
to help ensure the integrity of transactions made by X++
programmers. If the integrity of transactions is not ensured, it
may lead to data corruption, or, at best, poor scalability with
reference to concurrent users on the system. forUpdate Checking
This check ensures that no record can be updated or deleted if
the record has not first been selected for update. A record can be
selected for update, either by using the forUpdate keyword in the
select statement, or by using the selectForUpdate method on tables.
ttsLevel Checking
This check ensures that no record can be updated or deleted
except from within the same transaction scope as it was selected
for update. Integrity is ensured by using the following statements:
ttsBegin: marks the beginning of a transaction. This ensures data
integrity, and guarantees that all updates performed until the
transaction ends (by ttsCommit or ttsAbort) are consistent (all or
none). ttsCommit: marks the successful end of a transaction. This
ends and commits a transaction. MorphX guarantees that a committed
transaction will be performed according to intentions. ttsAbort:
allows you to explicitly discard all changes in the current
transaction. As a result, the database is rolled back to the
initial statenothing will have been changed. Typically, you will
use this if you have detected that the user wants to break the
current job. Using ttsAbort ensures that the database is
consistent. Note
It is usually better to use exception handling instead of
ttsAbort. The throw statement automatically aborts the current
transaction.
Statements between ttsBegin and ttsCommit may include one or
more transaction blocks as shown in the following example.
ttsBegin;// Some statements.ttsBegin;//
Statements.ttsCommit;ttsCommit;In such cases, you should note that
nothing is actually committed until the successful exit from the
final ttsCommit. Examples
Example use of ttsBegin and ttsCommitCopyCusttable
custTable;;ttsBegin; select forUpdate custTable where
custTable.AccountNum == '4000';custTable.NameAlias =
custTable.Name;custTable.update(); ttsCommit;Examples of Code
Rejected by the two Transaction Integrity ChecksCopyttsBegin;
select myTable; // Rejected by the forUpdate check.mytable.myField
= 'xyz';myTable.update(); ttsCommit; ttsBegin; select forUpdate *
from myTable;myTable.myField =
'xyz';ttsCommit;...ttsBegin;myTable.update(); // Rejected by the
ttsLevel check. ttsCommit;The first failure is because the
forupdate keyword is missing. The second failure is because the
update is in another transaction scope rather than the one the that
record was selected in ttsCommit for update. Exception HandlingYou
can write your X++ code to handle errors by using the statements
for generating and handling exceptions. For example, your method
might receive an input parameter value that is invalid. Your method
can throw an exception to immediately transfer control to a catch
code block that contains logic to handle this particular error
situation. You do not necessarily need to know the location of the
catch block that will receive control when the exception is thrown.
What is an Exception?
An exception is a regulated jump away from the regular sequence
of program instruction execution. The instruction at which program
execution resumes is determined by try - catch blocks and the type
of exception that is thrown. In X++, an exception is represented by
a value of the enum named Exception. A frequently thrown exception
is Exception::error enumeration value. This exception is thrown in
a variety of situations. It is common practice to write diagnostic
information to the Infolog before throwing the exception, and the
Global::error method is often the best way to do that. Exception
Related X++ Statements
You use the following X++ statements to generate and handle
exceptions: throw try catch retryNote
There is no finally statement in X++.
The throw Statement with an Exception MemberYou can use the
throw keyword to throw an Exception enum value. For example, the
following statement throws an enum value as an exception: throw
Exception::error;The throw Statement with the Global::error
MethodInstead of throwing an enum value, it is a best practice to
use the Global::error method output as the operand for throw: throw
Global::error("The parameter value is invalid.");The Global::error
method can automatically convert a label into the corresponding
text. This helps you to write code that can be more easily
localized. throw Global::error("@SYS98765");Tip
In X++ code, the static methods on the Global class can be
called without the Global:: prefix. For example, the Global::error
method can be called simply as error("My message.");.
The throw Statement without an OperandInside a catch block, you
can write the throw; statement without specifying anything else in
the statement. This re-throws the same exception value that the
catch block caught. You might re-throw an exception when your
method has no other safe way to continue. The try and catch
StatementsWhen an exception is thrown, it is first processed
through the catch list of the innermost try block. If a catch is
found that handles the kind of exception that is being thrown,
program control jumps to that catch block. If the catch list has no
block that specifies the particular exception, the system passes
the exception to the catch list of the next innermost try block.
The catch statements are processed in the same sequence that they
appear in the X++ code. It is common to have the first catch
statement handle the Exception::Error enumeration value. One
strategy is to have the last catch statement leave the exception
type unspecified. This means it handles all exceptions that are not
handled by a previous catch. This strategy is appropriate for the
outermost try - catch blocks. try { /* Code here. */ }catch
(Exception::Numeric) { info("Caught a Numeric exception."); }catch
{ info("Caught an exception."); }The retry StatementThe retry
statement can be written only in a catch block. The retry statement
causes control to jump up to the first line of code in the
associated try block. Caution
You must prevent your use of retry from causing an infinite
loop. The early statements in your try block must contain an if
test of a variable that eventually ends the looping.
The retry statement is used when the cause of the exception can
be fixed by the code in the catch block. The retry statement gives
the code in the try block another chance to succeed. Note
The retry statement erases messages that were written to the
Infolog since program control entered the try block.
The System Exception Handler
If no catch statement handles the exception, it is handled by
the system exception handler. The system exception handler does not
write to the Infolog. This means that an unhandled exception can be
hard to diagnose. Therefore we recommended that you do all the
following to provide effective exception handling: Have a try block
that contains all your statements in the outermost frame on the
call stack. Have an unqualified catch block at the end of your
outermost catch list. Avoid throwing an Exception enum value
directly. Do throw the enum value that is returned from one of the
following methods on the Global class (you have the option of
omitting the implicit Global:: prefix): Global::error
Global::warning Global::info When you catch an exception that has
not been displayed in the Infolog, call the Global::info function
to display it. Tip
Exception::CLRError, Exception::UpdateConflictNotRecovered, and
system kernel exceptions are examples of exceptions that are not
automatically displayed in the Infolog.
Exceptions and CLR Interop
From X++ you can call .NET Framework classes and methods that
reside in assemblies that are managed by the common language
runtime (CLR). When a .NET Framework System.Exception instance is
thrown, your code can catch it by referencing Exception::CLRError.
Your code can obtain a reference to the System.Exception instance
by calling the CLRInterop::getLastException method. Ensure
Exceptions are DisplayedExceptions of type Exception::CLRError are
not displayed in the Infolog. These exceptions are not issued by a
call to a method such as Global::error. In your catch block, your
code can call Global::error to report the specific exception.
Global Class Methods for Exceptions
This section describes some Global class methods in more detail.
The Global::error Method ParametersThe error method is declared as
follows: Copyserver client static Exception error (SysInfoLogStr
txt, URL helpURL = '', SysInfoAction _sysInfoAction = null)The
return type is the Exception::Error enum value. The error method
does not throw an exception. It only provides an enum value that
could be used in a throw statement. The throw statement throws the
exception. Only the first parameter is required. The parameters are
described in the following table. Parameter Description
SysInfoLogStrtxtA str of the message text. This can also be a
label reference, such as strFmt("@SYS12345", strThingName).
URLhelpUrlA reference to the location of a Help topic in the
Application Object Tree (AOT). For example:
"KernDoc:\\\\Functions\\substr"This parameter value is ignored if
_sysInfoAction is supplied.
SysInfoAction_sysInfoActionAn instance of a class that extends
the SysInfoAction class. The following list shows the method
overrides we recommend for the child class: description run pack
unpackFor sample code that uses SysInfoAction, see Sample 6.
The Global::info MethodThe Global::info method is routinely used
to display text in the Infolog. It is often written in programs as
just info("My message.");. Even though the info method returns an
Exception::Info enumeration value it would be rare to want to throw
an Exception::Info because nothing unexpected has occurred. The
Global::exceptionTextFallThrough MethodOccasionally you want to do
nothing inside your catch block. The X++ compiler issues a warning
when you have an empty catch block. You should avoid this warning
by calling the Global::exceptionTextFallThrough method in the catch
block. The method does nothing, but it satisfies the compiler.
Exceptions Inside Transactions
If an exception is thrown inside a transaction, the transaction
is automatically aborted (a ttsAbort operation occurs). This
applies both for exceptions thrown manually and for exceptions
thrown by the system. When an exception is thrown inside a ttsBegin
- ttsCommit transaction block, no catch statement inside that
transaction block can process the exception. Instead, the innermost
catch statements that are outside the transaction block are the
first catch statements to be tested. Code Samples
The next sections have the following code samples: Sample 1:
Display exceptions in the infolog Sample 2:error method to write
exception to infolog Sample 3: Handle a CLRError Sample 4: Use of
the retry statement Sample 5: Exception thrown inside a transaction
Sample 6: throw Global::error with a SysInfoAction parameter Sample
1: Display Exceptions in the Infolog
This X++ code sample shows that a direct throw of
Exception::Error does not display a message in the Infolog. That is
why we recommend the Global::error method. Copystatic void
TryCatchThrowError1Job(Args _args){/*** The 'throw' does not
directly add a message to the Infolog. The exception is caught.***/
; try { info("In the 'try' block. (j1)"); throw Exception::Error; }
catch (Exception::Error) { info("Caught 'Exception::Error'.");
}/********** Actual Infolog outputMessage (03:43:45 pm)In the 'try'
block. (j1)Caught 'Exception::Error'.**********/}Sample 2: error
Method to Write Exception to Infolog
The sample shows that use of the Global::error method is a
reliable way to display exceptions in the Infolog. Copystatic void
TryCatchGlobalError2Job(Args _args){/*** The 'Global::error()' does
directly add a message to the Infolog. The exception is caught.***/
; try { info("In the 'try' block. (j2)"); throw
Global::error("Written to the Infolog."); } catch
(Exception::Error) { info("Caught 'Exception::Error'.");
}/********** Actual Infolog outputMessage (03:51:44 pm)In the 'try'
block. (j2)Written to the Infolog.Caught
'Exception::Error'.**********/}Sample 3: Handle a CLRError
This sample shows that a CLRError exception is not displayed in
the Infolog (unless you catch the exception and manually call the
info method). The use of the CLRInterop::getLastException method is
also demonstrated. Copystatic void TryCatchCauseCLRError3Job(Args
_args){/*** The 'netString.Substring(-2)' causes a CLRError,but it
does not directly add a message to the Infolog. The exception is
caught.***/ System.String netString = "Net string.";
System.Exception netExcepn; ; try { info("In the 'try' block.
(j3)"); netString.Substring(-2); // Causes CLR Exception. } catch
(Exception::Error) { info("Caught 'Exception::Error'."); } catch
(Exception::CLRError) { info("Caught 'Exception::CLRError'.");
netExcepn = CLRInterop::getLastException();
info(netExcepn.ToString()); }/********** Actual Infolog output
(truncated for display)Message (03:55:10 pm)In the 'try' block.
(j3)Caught
'Exception::CLRError'.System.Reflection.TargetInvocationException:
Exception has been thrown by the target of an invocation. --->
System.ArgumentOutOfRangeException: StartIndex cannot be less than
zero.Parameter name: startIndex at
System.String.InternalSubStringWithChecks(Int32 startIndex, Int32
length, Boolean fAlwaysCopy) at System.String.Substring(Int32
startIndex)
at ClrBridgeImpl.InvokeClrInstanceMethod(ClrBridgeImpl* ,
ObjectWrapper* objectWrapper, Char* pszMethodName, Int32
argsLength, ObjectWrapper** arguments, Boolean* argsAreByRef,
Boolean* isException)**********/}For more information, see How to:
Catch Exceptions Thrown from CLR Objects. Sample 4: Use of the
retry Statement
This sample shows how to use the retry statement. The print
statements are included because retry causes earlier Infolog
messages to be erased. Copystatic void TryCatchRetry4Job(Args
_args){/*** Demonstration of 'retry'. The Infolog output is
partially erasedby 'retry', but the Print window is fully
displayed.***/ Exception excepnEnum; int nCounter = 0; ; try {
info(" ."); print(" ."); info("In the 'try' block, [" +
int2str(nCounter) + "]. (j4)"); print("In the 'try' block, [" +
int2str(nCounter) + "]. (j4)"); pause; nCounter++; if (nCounter
>= 3) // Prevent infinite loop. { info("---- Will now throw a
warning, which is not caught."); print("---- Will now throw a
warning, which is not caught."); pause; throw Global::warning("This
warning will not be caught. [" + int2str(nCounter) + "]"); } else {
info("Did not throw a warning this loop. [" + int2str(nCounter) +
"]"); print("Did not throw a warning this loop. [" +
int2str(nCounter) + "]"); } excepnEnum = Global::error("This error
message is written to the Infolog."); throw excepnEnum; } catch
(Exception::Error) { info("Caught 'Exception::Error'.");
print("Caught 'Exception::Error'."); retry; } info("End of job.");
print("End of job."); pause;/********** Actual Infolog
outputMessage (04:33:56 pm) .In the 'try' block, [2]. (j4)---- Will
now throw a warning, which is not caught.This warning will not be
caught. [3]**********/}Sample 5: Exception Thrown Inside a
Transaction
This sample uses three levels of try nesting to illustrate where
an exception is caught when the exception is thrown inside a
ttsBegin - ttsCommit transaction block. Copystatic void
TryCatchTransaction5Job(Args _args){/*** Shows an exception that is
thrown inside a ttsBegin - ttsCommittransaction block cannot be
caught inside that block.***/ ; try { try { ttsbegin; try { throw
error("Throwing exception inside transaction."); } catch
(Exception::Error) { info("Catch_1: Unexpected, caught in 'catch'
inside the transaction block."); } ttscommit; } catch
(Exception::Error) { info("Catch_2: Expected, caught in the
innermost 'catch' that is outside of the transaction block."); } }
catch (Exception::Error) { info("Catch_3: Unexpected, caught in
'catch' far outside the transaction block."); } info("End of
job.");/********** Actual Infolog outputMessage (04:12:34
pm)Throwing exception inside transaction.Catch_2: Expected, caught
in the innermost 'catch' that is outside of the transaction
block.End of job.**********/}Sample 6: use Global::error with a
SysInfoAction parameter
When your code throws an exception, your code can write messages
to the Infolog window. You can make those Infolog messages more
helpful by using the SysInfoAction class. In the following X++ code
sample, a SysInfoAction parameter is passed in to the Global::error
method. The error method writes the message to the Infolog. When
the user double-clicks the Infolog message, the SysInfoAction.run
method is run. You can write code in the run method that helps to
diagnose or fix the problem that caused the exception. The object
that is passed in to the Global::error method is constructed from a
class that you write that extends SysInfoAction. The following code
sample is shown in two parts. The first part shows a job that calls
the Global::error method, and then throws the returned value. An
instance of the SysInfoAction_PrintWindow_Demo class is passed into
the error method. The second part shows the
SysInfoAction_PrintWindow_Demo class. Part 1: The Job that calls
Global::errorCopystatic void Job_SysInfoAction(Args _args){ ; try {
throw Global::error ("Click me to make the Print window display."
,"" ,new SysInfoAction_PrintWindow_Demo() ); } catch {
warning("Issuing a warning from the catch block."); }}Part 2: the
SysInfoAction_PrintWindow_Demo classCopypublic class
SysInfoAction_PrintWindow_Demo extends SysInfoAction{ str
m_sGreeting; // In classDeclaration.
public str description() { ; return "Starts the Print Window for
demonstration."; }
public void run() { ; print("This appears in the Print
window."); print(m_sGreeting); pause;
/*********** Actual Infolog outputMessage (03:19:28 pm)Click me
to make the Print window display.Issuing a warning from the catch
block.***************/ }
public container pack() { ; return ["Packed greeting."]; //
Literal container. }
public boolean unpack (container packedClass , Object object =
null ) { ; [m_sGreeting] = packedClass; return true; }}List of
Exceptions
The exception literals shown in the following table are the
values of the Exception System Enumeration. Exception literal
Description
BreakIndicates that the user has pressed BREAK or CTRL+C.
CLRErrorIndicates that an error has occurred during the use of
the common language runtime (CLR) functionality.
CodeAccessSecurityIndicates that an error has occurred during
the use of the CodeAccessPermission.demand method. For more
information, see Code Access Security.
DDEerrorIndicates that an error occurred in the use of the DDE
system class.
DeadlockIndicates that there is a database deadlock because
several transactions are waiting for each other.
DuplicateKeyExceptionIndicates that an error has occurred in a
transaction that is using Optimistic Concurrency Control. The
transaction can be retried (use a retry statement in the catch
block).
DuplicateKeyExceptionNotRecoveredIndicates that an error has
occurred in a transaction that is using Optimistic Concurrency
Control. The code will not be retried. Note This exception cannot
be caught inside a transaction.
ErrorIndicates that a fatal error has occurred. The transaction
has been stopped.
InfoHolds a message for the user. Do not throw an info
exception.
InternalIndicates an internal error in the development
system.
NumericIndicates that an error has occurred during the use of
the str2int, str2int64, or str2num functions.
Sequence(TBD)
UpdateConflictIndicates that an error has occurred in a
transaction that is using Optimistic Concurrency Control. The
transaction can be retried (use a retry statement in the catch
block).
UpdateConflictNotRecoveredIndicates that an error has occurred
in a transaction that is using Optimistic Concurrency Control. The
code will not be retried. Note This exception cannot be caught
within a transaction.
WarningIndicates that something exceptional has happened. The
user might have to take action, but the event is not fatal. Do not
throw a warning exception.
Exception Handling for User ControlsCode you add to User
Controls may call methods that throw exceptions. It is important
that your code correctly handle any exceptions encountered.
Unhandled exceptions create a poor experience for the user. The
Enterprise Portal framework can help you manage exceptions.
Exception Categories
Exceptions in Enterprise Portal are divided into three
categories. These exception categories are defined in the
enumeration AxExceptionCategory. They are described in the
following table. Exception Category Description
NonFatal Indicates an exception that was expected. The exception
handling code should respond appropriately and allow for the
request to continue normally.
AxFatal Indicates that an unrecoverable error has occurred in
Enterprise Portal. Enterprise Portal content will not display.
Non-portal content should display as expected.
SystemFatal Indicates a serious error has occurred and the
request must be aborted. Errors of this kind often cause an HTTP
error code 500.
When Exception Handling Is Needed
You must handle exceptions when code you add to a User Control
directly calls code that can throw exceptions. The following are
common situations where this occurs. Your code directly calls
methods from the Enterprise Portal framework that can throw
exceptions. For instance, the EndEdit method for a DataSetViewRow
object can encounter exceptions as a result of the edit operation.
If your code called this method directly, it must handle the
exceptions. Your code calls X++ methods through the proxy, and the
X++ methods throw exceptions. Your code must handle the exceptions.
The controls for Enterprise Portal have built-in functionality to
perform standard actions such as editing a row in a grid or saving
the contents of a form. The code for this built-in functionality
properly handles exceptions. If the action started with the
built-in functionality of a control, such as a user clicking the OK
button for an AxForm, you do not need to add code to handle
exceptions for that action. Exception Handling Code
The code to handle exceptions for User Controls in Enterprise
Portal has the following basic form. This code calls the
TryHandleException method to determine the category of the
Enterprise Portal exception. Based on the category, the code will
try to correctly respond to the exception. Copytry{ // Code that
may encounter exceptions goes here.}catch (System.Exception ex){
AxExceptionCategory exceptionCategory;
// Determine whether the exception can be handled. if
(AxControlExceptionHandler.TryHandleException(this, ex, out
exceptionCategory) == false) { // The exception was fatal and
cannot be handled. Rethrow it. throw; }
if (exceptionCategory == AxExceptionCategory.NonFatal) { //
Application code to properly respond to the exception goes here.
}}Note
The Enterprise Portal framework will automatically display the
exception message in the Infolog Web part on the page.
The code that runs in response to the exceptions should leave
Enterprise Portal in a valid state. For example, if a validation
exception occurs when the user clicks the OK button to save the
values on a page, data will not be saved. The code in the exception
handler should prevent Enterprise Portal from redirecting to a
different page. This allows for the user to see the error on the
page, correct it, and then perform the action again. Exception
Example
The CalculateGrade method is an X++ method that is part of the
Test class added to Microsoft Dynamics AX. The method takes a
numeric grade value and returns the corresponding letter grade. If
the input value is outside the valid range (0 to 100), an exception
is thrown. The proxy was used to make this method available to User
Controls. The following example is the click method for a button
added to a User Control. It calls the CalculateGrade method to
retrieve a letter grade based on a test score. The code handles the
exception that occurs if the input value is out of range. In this
case, it displays text that indicates the grade is out of range.
Copyprotected void Button1_Click(object sender, EventArgs e){
string letterGrade;
// Method can throw an exception, so an exception handler is
used. try { letterGrade =
Test.CalculateGrade(this.AxSession.AxaptaAdapter,
Convert.ToInt16(TextBoxScore.Text)); TextBoxGrade.Text =
letterGrade; } catch (System.Exception ex) { AxExceptionCategory
exceptionCategory;
if(AxControlExceptionHandler.TryHandleException(this, ex, out
exceptionCategory) == false) { // The exception was fatal, so
rethrow it. throw; }
if(exceptionCategory == AxExceptionCategory.NonFatal) { if
(ex.InnerException.Message.Equals("Grade is out of range")) {
TextBoxGrade.Text = ""; } } }}insert Table MethodThe xRecord.insert
method generates values for RecId and system fields, and then
inserts the contents of the buffer into the database. The method
operated as follows: Only the specified columns of those rows
selected by the query are inserted into the named table. The
columns of the table being copied from and those of the table being
copied to must be type compatible. If the columns of both tables
match in type and order, the column-list may be omitted from the
insert clause. The insert method updates one record at a time. To
insert multiple records at a time, use array inserts,
insert_recordset, or RecordSortedList.insertDatabase. To override
the behavior of the insert method, use the doInsert method. Example
1
The following code example inserts a new record into the
CustTable table, with the AccountNum set to 5000 and the Name set
to MyCompany (other fields in the record will be blank).
CopyCustTable custTable;;ttsBegin; select forUpdate
custTable;custTable.AccountNum = '5000';custTable.Name =
'MyCompany';custTable.insert(); ttsCommit;Example 2: Transaction
and Duplicate Key
The following example shows how you can catch a
DuplicateKeyException in the context of an explicit transaction.
The exception is thrown when a call to xRecord.insert fails because
of a duplication of an existing unique value. In the catch block,
your code can take corrective action, or it can log the error for
later analysis. Then your code can continue without losing all the
pending work of the transaction. Note
You cannot catch a duplicate key exception caused by a set based
operation such as insert_recordset.
This example depends on two tables TableNumberA and
TableNumberB. Each has one mandatory Integer field, named
NumberAKey and NumberBKey respectively. Each of these key fields
has a unique indexed defined on it. The TableNumberA table must
have at least one record in it. static void
JobDuplicKeyException44Job(Args _args){TableNumberA tabNumA; // Has
one record, key = 11.TableNumberB tabNumB;AddressState
tabAddressState;int iCountTries = 0,iNumberAdjust =
0,iNewKey,ii;container ctNotes;;// Empty the B table.delete_from
tabNumB;// Insert a copy of one record.insert_recordset tabNumB
(NumberBKey)select firstOnly NumberAKey from tabNumAorder by
NumberAKey asc;ttsBegin;try{iCountTries++;ctNotes += strFmt("----
Inside the try block, try count is %1. ----",iCountTries);while
select * from tabNumAorder by NumberAKey asc{tabNumB
.clear();iNewKey = tabNumA .NumberAKey + iNumberAdjust;tabNumB
.NumberBKey = iNewKey;ctNotes += strFmt("-- %1 is the key to be
tried. --" ,iNewKey);tabNumB .insert();ctNotes += "-- .insert()
successful. --";break; // Keeps demo simple.}ttsCommit;}catch
(Exception ::DuplicateKeyException,tabNumB) // Table is
optional.{ctNotes += "---- Inside the catch block. ----";ctNotes +=
infolog .text();if (iCountTries