Catching exceptions in AX 7

In this blog post, I briefly recapitulate how to throw and catch exceptions in X++ and introduce a new way of handling CLR exceptions in AX 7.

X++ exceptions

When you want to throw an exception in X++, you typically do it by something like this:

throw error("It's broken!");

It’s a functional equivalent of adding a message to infolog and throwing Exception::Error.

infolog.add(Exception::Error, "It's broken");
throw Exception::Error; // number 3

As you see, an exception in X++ is (or used to be, as I’ll discuss later) just a number, because enums are backed by integer values. It doesn’t come with any extra information, not even the message. Adding messages to infolog is a separate process; you can add error messages to infolog without throwing any exceptions and throwing exceptions without adding anything to infolog.

When you want to catch an exception, you’ll use a catch clause with the right value of Exception enum (usually Exception::Error) or you’ll handle all exceptions (by a catch clause without any type).

The following statement will work for all errors, but you don’t get any information about what error it was:

catch (Exception::Error) {}

Just knowing that there is an error doesn’t allow you to react differently to different errors, therefore you’ll rarely see code in AX trying to recover from a particular error.

CLR exceptions (in general)

It’s very different in Common Language Runtime (CLR). Exceptions there are objects (instances of classes extending System.Exception) and they contain a lot of information that can help you to identify what happened. For example, you can look at the type of exception (e.g. ArgumentNullException or FileNotFoundException) and react accordingly. The error message is a part of the object too, not just lying somewhere in infolog. The exception also comes with the stack trace, so you can easily see where it was thrown from.

Because the classes form a hierarchy, you can also handle exceptions in a hierarchic way. This is what you can write in C#:

catch (FileNotFoundException ex) {}
catch (Exception ex) {}

The system will try catch clauses one by one, going from the top down, and will use the first compatible clause it finds. If a FileNotFoundException is thrown, the first block will be used, but all other exceptions will go to the latter one, because they’re not FileNotFoundException but they’re all compatible with System.Exception, which its their common base class.

Exception::CLRError

Sometimes you have to handle CLR exception in X++, because you can use .NET libraries from X++ and such libraries can throw their usual (CLR) exceptions. The traditional approach is catching Exception::CLRError, getting the exception object from CLRInterop::getLastException() and extracting information from it. It’s further complicated by the fact that the actual error is usually wrapped in TargetInvocationException.

This is how you can handle FileNotFoundException in X++:

try
{
    System.IO.File::ReadAllText('c:\\nothing.here');
}
catch (Exception::CLRError)
{
    System.Exception wrapperEx = CLRInterop::getLastException() as System.Reflection.TargetInvocationException;
    if (wrapperEx)
    {
        if (wrapperEx.InnerException is System.IO.FileNotFoundException)
        {
            warning(wrapperEx.InnerException.Message);
        }      
    }
}

It works, but it’s cumbersome and hard to understand.

Catch with object reference

Fortunately AX 7 offers a new, much better option. Let me start by refactoring the previous example:

System.IO.FileNotFoundException fileNotFoundEx;
 
try
{
    System.IO.File::ReadAllText('c:\\nothing.here');
}
catch (fileNotFoundEx)
{            
    warning(fileNotFoundEx.Message);
}

This is obviously much shorter and easier to follow. In catch, I don’t have to use only values of the Exception enum anymore, I can also use exception objects. The system checks the type, selects the right catch clause and passes the exception object there. It’s almost the same as in C#, except of the fact that you can’t define the exception type directly in the catch condition; you have to declare a variable in an outer scope.

As in C#, you may have several catch clauses for different types of exceptions and benefit from the hierarchical nature of exception types. For example, the following snippet handles two types of exceptions in a special way and all remaining exceptions go to the last catch clause.

System.Exception                generalEx;
System.FieldAccessException     accessEx;
System.IO.FileNotFoundException fileNotFoundEx;
 
try
{}
catch (accessEx)
{
    warning("Field access");
}
catch (fileNotFoundEx)
{
    warning("File not found");
}
catch (generalEx)
{	
    warning(ex.Message);
}

ErrorException

All right, so X++ errors can be caught with catch (Exception::Error) and CLR exceptions by their particular type, but isn’t all code now executed by CLR? Does it mean that X++ exceptions use a different mechanism than CLR exceptions, or they’re just normal CLR exceptions under the hood?

You can find the answer in debugger, which reveals that “native” AX exceptions are indeed implemented as CLR exceptions:

The object (as you can see in debugger) has all usual properties, such as Message, Source, StackTrace and so on. Note that you can see the same behavior in AX 2012 if you debug CIL generated from X++.

Now what if you want to access the properties when handling an exception, e.g. to include the stack trace in a log? If you catch Exception::Error, you won’t get any details, but what if you try to catch ErrorException in the same way as any other CLR exception? If you think it should be possible, you’re right – the following piece of code successfully catches an X++ exception and shows how you can access its properties.

Microsoft.Dynamics.Ax.Xpp.ErrorException xppEx;
 
try
{
    throw error("It's broken!");
}
catch (xppEx)
{
    this.log(xppEx.StackTrace);
}

The problem that all X++ errors have the same exception type (ErrorException) is still there, therefore handling different errors in different ways is still hard, but you can now easily find which message belongs to the exception (without digging into infolog), where it was thrown from and so on.

By the way, I also wondered what would happen if I tried to catch both Exception::Error and ErrorException, because they’re internally the same thing.

Microsoft.Dynamics.Ax.Xpp.ErrorException xppEx;
 
try  
{
    throw Exception::Error;
} 
catch (Exception::Error)
{
    info("Exception::Error");
}
catch (xppEx)
{
    info("ErrorException");
}

Such code compiles without any problem and the resulting CIL will actually contains two catch clauses for the same exception type (ErrorException). It means that the top one always wins, regardless whether it’s catch (Exception::Error) or catch (xppEx). Using both for the same try statement makes little sense, but at least we now know that nothing catastrophic happens if somebody does it by mistake.

Conclusion

The native implementation of exceptions in X++ is very limited in comparison to CLR exceptions. The inability to distinguish between different kinds of errors makes meaningful recovery from errors virtually impossible and the lack of associated information (such as the message) makes even mere logging quite cumbersome. This doesn’t apply to CLR exceptions and their handling in X++ is even easier than before, thanks to the new ability of catch clauses. The fact that we can use this approach for X++ exception as well means that we can easily work around one of the limitations and access properties such as message and stack trace even for them.

It seems that we’re just a small step from being able to work with exceptions in X++ in the same way as in .NET. If we could throw exception objects (e.g. throw new MissingRecordException()), we would be able catch this particular type of exception and implement more robust logic for recovery from errors.

It would also help if X++ was extended to support declaration of the exception type directly in catch (e.g. catch (MissingRecordException ex)).

16 Comments

  1. Martin,

    Great post and a very interesting find.
    “the following piece of code successfully catches an X++ exception and shows how you can access its properties.” – this has a lot of potential.

    Best Regards,

  2. Useful info Martin thanks.
    Do you think that it would be possible to tie it into the error class so that the exception is passed?

    here is a sample of code i’m toying with:

    [PreHandlerFor(classStr(Global), staticMethodStr(Global, error))]
    public static void Global_Pre_error(XppPrePostArgs args)
    {
    //str message = Args.getArg(‘txt’);
    //str exception = message + con2Str(xSession::xppCallStack());
    Microsoft.Dynamics.Ax.Xpp.ErrorException xppEx;

    xppEx = Args.getArg(“exception”); // Pseudo example of what i’m trying – i want to pass this exception to a logging tool (as a C# exception)

    • It’s not gonna work, because your code runs before the exception is thrown, therefore no such object has been created yet.
      In fact, error() isn’t throwing any exception at all; it merely adds a message to infolog and returns Exception::Error. Exceptions are thrown by the throw statement.

  3. Moving some code from C# to X++ so improved exception handling helps. Using your example, I did a throw error(“It’s broken!”); and could then pick up that string from xppEx.Message in the catch clause. But it was delivered as an error in the infolog as well. Is there a way to throw a string so that it is only sent to the catch, and not to the infolog?

  4. Thanks for the helpful post. I seem to have a situation where although Dynamics recognizes that an error of type Microsoft.Dynamics.Ax.Xpp.ErrorException was thrown (it jumps to the correct catch() statement), the system still does not go within the catch and does not handle the error:

    Microsoft.Dynamics.Ax.Xpp.ErrorException was unhandled by user code
    Message: An exception of type ‘Microsoft.Dynamics.Ax.Xpp.ErrorException’ occurred in Dynamics.AX.CFBS_Ext.Class.PMF_ImportStagingData_Cust.netmodule but was not handled in user code

    here is the catch:
    catch(xppEx)
    {
    warning(strfmt(“%!”,xppEx.StackTrace));
    recordCountmax++;
    }

    Any ideas why this wouldn’t work?

    Thanks,
    Don Shields

    • Hey Don,

      i was facing the same in our custom logic that we migrated from AX 2012. Here’s the fix to prevent the message “Could not open menu item XXX” .. .In your catch replace the throw error(“something”) by infolog.add(Exception::Error, “something”). It should look like below –
      catch (Exception::Error)
      {
      infolog.add(Exception::Error, “something”);
      }

      Thank martin for guidance.

      • Dinesh, Don doesn’t have any throw error in this catch block. What are you talking about?
        Note that infolog.add(Exception::Error, “something”) is the same as error(“something”), which is clearly more readable. Notice that error() can be used without throw. I’ve discussed the behaviour of error() at the beginning of my blog post.

        • Martin, i just provided a workaround for “An exception of type ‘Microsoft.Dynamics.Ax.Xpp.ErrorException’ occurred in Dynamics.AX.CFBS_Ext.Class.PMF_ImportStagingData_Cust.netmodule but was not handled in user code” because the control does not go inside the catch-
          catch(xppEx)
          {
          warning(strfmt(“%!”,xppEx.StackTrace));
          recordCountmax++;
          }
          Do we have an idea why the control does not go inside above mentioned catch statement?

          Thank you

  5. Hello,
    I do the following:
    XDocument xmlData;
    System.Xml.XmlException xmlEx;
    //Microsoft.Dynamics.Ax.Xpp.ErrorException xmlEx;

    try
    {
    xmlData = XDocument::Load(stream);
    }
    catch(xmlEx)
    {

    warning(xmlEx.Message);
    }

    If there is no well-formed xml, it throws an exception.
    But the system always shows the message:

    An exception of type ‘System.Xml.XmlException’ occurred in myModule.netmodule but was not handled in user code
    Any ideas why it does not catch the exception correctly?

    • At the first thing, verify that your error-handling code gets called at all. It won’t if it’s inside a transaction.

  6. Hi, how to cathing this error?
    System.Net.WebException
    I have this error:
    System.Net.WebException was unhandled by user code
    Message: An exception of type ‘System.Net.WebException’ occurred in System.dll but was not handled in user code
    Additional information: Unable to connect to the remote server
    But i have to save this error in log register.
    Thanks.

  7. Hi Martin,

    Really good blog!

    Probably a stupid question but I want to capture the AX error message and assign it to a string variable to return via a data contract for example.

    str errorMsg;

    try
    {
    // post a purchase order invoice for example
    }
    catch(Exception::error)
    {
    // invoice encounters an error therefore I want to return this error message text to errorMsg;

    errorMsg = ???????
    }

    • As I explained above, if you catch Exception::Error, you don’t get any other information. All you can do is getting messages from infolog.
      The way to get an exception object is catching ErrorException instead of Exception::Error. The example in my blog post logs StackTrace property, but you can easily use Message instead.

  8. Hi Martin,

    Your blog is very helpful , but I am stuck to get the Exception for below type, InvalidAttributePathException is not showing x++ class so we are not able to create object of this type. Since not able to catch this error it is showing error on the form and not able to log.
    Microsoft.Dynamics.Ax.Frameworks.Controls.ProductConfiguration.InvalidAttributePathException

Comments are closed.