In my last post I have introduced the GETLASTERROROBJECT function that returns you the instance of the System.Exception class, representing the actual exception that has happened.
To properly handle exceptions in an unambiguous way, you must use the exception type, not its name, so it is important to get the actual System.Type representing the exception type.
Sounds easy, but it’s not quite so.
Getting a System.Type of something is easy-peasy, with C/AL or C# alike. To get an actual type of an exception in a block of an error-handling code, you could write something along the following lines:
And it shows something like this:
(Please, make sure not to use the GETDOTNETTYPE on the Exception variable, because if you declared it as System.Exception, it will always return System.Exception. GETDOTNETTYPE always returns the declared type, not the runtime type – in this respect it is very similar to the typeof keyword in C#)
So far, so good.
Now we can store tat type away in a variable and use it in our error handling code, which should go about somewhat like this:
Yes, exactly, what do we test against? If you say the following, then you are wrong:
You are wrong because you are accessing the name of the exception, and it is not guaranteed to be unique. If you are only interested in proper unambiguous exception handling code, then this is no better than GETLASTERRORCODE.
The correct way would be this:
This NavNCLDialogExceptionType is obviously of System.Type type, but how do we get an instance of it to represent the actual Microsoft.Dynamics.Nav.Types.Exceptions.NavNCLDialogException type? If you thought of declaring a variable of this type – you cannot do that directly, unless you copy the appropriate assembly to the add-ins folder, which I recommend against.
There is a workaround though. Since all of NAV exceptions are thrown as instances in the Microsoft.Dynamics.Nav.Types.Exceptions (in fact, they are all descendants of the NavBaseException type) and are all declared in the same assembly, it is easy to get hold of the instance of the System.Reflection.Aseembly class and then access any type using it’s full name from that assembly.
So, the code in the end would look like this:
If you run it, and the error thrown in the codeunit 50009 is a result of the ERROR function, you’ll see this message:
A legitimate question you may have at this time is why is obtaining a type by its name guaranteed to be unique, while simply checking the type name against a string value is not guaranteed to be unique. It’s actually very simple: a type within an assembly must have a unique name; two different types in two different assemblies can have the same name. Since I have used the Assembly.GetType method, it is taking the type by name from the assembly that declares it, hence it’s unique.
Even though it requires some coding, and it adds a bit of reflection overhead on top of everything, it is guaranteed to give you an unambiguous identification of each exception, and the overhead is negligible.
And final legitimate objection might be – since I’ve said that all of the exceptions thrown by NAV are descendants of NavBaseException, why do I need to bother with reflection? If it is so, why is comparing against the type name potentially wrong. Well, in case you are just checking the exception resulting from the GETLASTERROROBJECT, then you are probably as safe checking the name as you are checking the type, but there is more to exceptions than what GETLASTERROROBJECT gives you directly, there are inner exceptions which give you more insight, and you would often need to check those, and these can come from a large set of possible assemblies, therefore you need to have a way to uniquely, unambiguously identify the exception type. I will blog about this in another post.
Pingback: Getting the exception type from the GETLASTERROROBJECT – 10/14, Navigate Into Success |
Hi,
It’s possible to generate a log with all errors?.
This code fails, but this is the aidea:
FOR index := 0 TO 3 DO BEGIN
IF NOT myCODEUNIT.FUNC_LAUNCHERROR(param) THEN BEGIN
Exception := GETLASTERROROBJECT;
MESSAGE(‘ERROR: ‘ + Exception.Message);
END;
END;
I am not really sure I follow. When calling a codeunit the way you show here, you always capture the error, and you can do whatever you want with it, log it in a file, database, event log, anything. Can you please clarify?
In the abobe case, you can capture the error but only once, because the loop will finish.
To be able of get all errors, I have replaced:
myCODEUNIT.FUNC_LAUNCHERROR(param) for:
myCODEUNIT.RUN
In this case I have to pass the parameters by another function, but it’s ok.