Exception handling with X++ and .NET Interop

A recent discussion on the community forum about exception messages prompted me to test the given case thoroughly and I think the results are worth sharing. The post also mentions something what I already found before – that error handling can be tricky in code which uses .NET Interop and which can be executed as both X++ and CIL.

Let’s introduce the problem. We have an X++ class method (AX2012) that throws an exception:

public class XppClass
{
    public static void run()
    {
        throw error("It failed!");
    }
}

It’s used in a .NET library (through a proxy class):

public class CSharpClass
{
    public static void Run()
    {
        try
        {
            XppClass.run();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

The .NET class is called from X++ again:

public static void main(Args args)
{
    try
    {
        CSharpClass::Run();
    }
    catch (Exception::CLRError)
    {
        info(AifUtil::getClrErrorMessage());
    }
    catch (Exception::Error)
    {
        info("Exception::Error caught");
    }
}

We’ll use exactly the same code in different contexts – and we’ll see that the behavior won’t be the same.

Client-bound X++

If we run the code on client, we can catch the exception in X++ as usual (i.e. “Exception::Error caught” will be shown in infolog). In the .NET class, the exception object has type Microsoft.Dynamics.AX.ManagedInterop.ErrorException and the infolog message is contained in the Message property:

ErrorException

That’s the behavior described in Proxies and Exception Mapping [AX 2012].

Server-bound X++

Now let’s run the same code on the server tier. The exception can be caught in X++ code as before. But the situation in the .NET class is different – although the type is still ErrorException, the Message property contains text “Exception of type ‘Microsoft.Dynamics.AX.ManagedInterop.ErrorException’ was thrown” instead of the actual infolog message (and the InnerException is empty).

CIL session

When running the code in a CIL session (such as in batch), the situation is even more different. The exception caught in the .NET class is System.Reflection.TargetInvocationException (with a generic message “Exception has been thrown by the target of an invocation”). Its inner exception is Microsoft.Dynamics.Ax.Xpp.ErrorException with an equally useless message “Exception of type ‘Microsoft.Dynamics.Ax.Xpp.ErrorException’ was thrown”.

The exception in X++ is TargetInvocationException too and its inner exception is the exception we saw in .NET. Because it’s a .NET exception, it won’t be caught by catch (Exception::Error) (which is, by the way, translated to catch (ErrorException) in CIL), we have to use catch (Exception::CLRError). That’s a really important observation – when you run such an X++ code in CIL, your error handling code may not work was expected.

Conclusion

As you can see, combining .NET Interop from X++ and .NET Interop to X++ may easily cause you a headache, because the behavior depends on execution tier and whether the code runs as X++ or CIL. In many cases, you also won’t get details about X++ exceptions.

You may want to take that into account when designing your solutions – maybe you don’t need to use .NET Interop from X++ and .NET Interop to X++ in the same time.