X++ coding standards

53
X++ Coding Standards General 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 stateent!, " the e#ception of s"itch cases, or "hen checking for start conditions. $eep the building blocks (ethods! sall and clear. % ethod should do a single, "ell&de'ned ob. )t should therefore be easy to nae a ethod. *ut braces around every block of stateents, even if there is only one stateent in the block. *ut coents in your code, telling others "hat the code is supposed to do, and "hat the paraeters are used for. Do not assign values to, or anipulate, actual paraeters that are +supplied+ by value. ou should al"ays be able to trust that the value of such a paraeter is the one initially supplied. -reat such paraeters as constants. Clean up your code; delete unused variables, ethods and classes. ever let the user e#perience a runtie error. -ake appropriate actions to either anage the situation prograatically or thro" an error inforing the user in the Infolog about the proble and "hat actions can be taken to '# the proble. ever ake assignents to the +this+ variable. %void dead code. ( ee Dead Code 0#aples.! 1euse code. %void using the sae lines of code in nuerous places. Consider oving the to a ethod instead. ever use infolog.add directly. 2se the indirection ethods: error, "arning, info a check3ailed. Design your application to avoid deadlocks. 4ore speci'c code standards are described belo": 566 layout Coents

description

X++ coding standards

Transcript of X++ coding standards

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