Custom Exception handling using business types part 1
All of you must be already familiar with exception handling in Dynamics 365 finops. We can throw an exception by using below statements.
1) throw exception:error; //Not so great without any message or other details
2) throw error(“Still not great”)// Still does not have type information
3) Using standard exception classes.throw new ArgumentNullException("Method 1 received a null argument on parameter 1!"); //Still no business info. Some business type info would be great.
4) Using custom new derived exception class.throw new CreditLimitException(message,stackinfo); //Even better as it has Business type and has stack and message details
So how can we handle exceptions according to number 4 in x++ ? See below.
In X++ we have ErrorException class which is basically number 1 and 2 above. This can be used in a catch statement like this
catch(Exception::Error)
{
info(con2Str(xSession::xppCallStack(),','));
}
Or
ErrorException ex;
try
{
}
catch(ex) //same type as catch(exception::error) above
{
info(ex.StackTrace);
}
We cannot do this
//catch(ErrorException ex) //Not supported error
// {
// }
To handle exceptions according to point 4 above we start by creating a new class FooException. What Foo here is irrelevant, it can be the actual business exception. It could be credit limit exceeded, vendor on hold ,inventory not available etc. What is important is it specifies a unique scenario or type of exception. Below is the code.
This is a class inheriting from ClrErrorException. The class is added in a class library which is referenced from a finops project. Assembly name is ExceptionsLib. ClrErrorException inherits from class XppException
namespace ExceptionsLib
{
[Serializable]
public class FooException :ClrErrorException
{
public FooException()
{
}
public FooException(string message): base(message)
{
XppException.SetInfologMessage(ExceptionKind.ExceptionClrError, message, string.Empty);
}
public FooException(string message,string stackTrace) : base(message)
{
FooStackTrace = stackTrace;
XppException.SetInfologMessage(ExceptionKind.ExceptionClrError, message, stackTrace);
}
public FooException(string message, bool sendToInfolog): base(message)
{
if (!sendToInfolog)
return;
XppException.SetInfologMessage(ExceptionKind.ExceptionClrError, message, string.Empty);
}
public FooException(string message, Exception innerException): base(message, innerException)
{
FooStackTrace = innerException.StackTrace;
XppException.SetInfologMessage(ExceptionKind.ExceptionClrError, message, innerException.StackTrace);
}
protected FooException(SerializationInfo info, StreamingContext context): base(info, context)
{
}
public string FooStackTrace
{
get;set;
}
}
}
Now to throw the exception from x++.
throw new FooException(“”);//Does not compile
The above wont work because x++ compiler will ask you to throw using the enum Exception like Exception::Error. There is another way to throw the FooException.
Create a new static method to throw the exception. I have added two static method to class FooException which can be used to throw the exception.
Class FooException
{
public static void throwFooException(string message,string trace)
{
throw new FooException(message,trace);
}
public static void throwFooException(string message)
{
throw new FooException(message);
}
}
Now the code to throw the exception and catch it will look like this
class FooClass
{
/// <summary>
/// Runs the class with the specified arguments.
/// </summary>
/// <param name = "_args">The specified arguments.</param>
public static void main(Args _args)
{
str funcvar= "fn scope";
ErrorException ex;
FooException fox;
try
{
str tryvar="tryscope";
info("In the 'try' block. (j1)");
FooException::throwFooException(“throw business exception”);
}
//catch(FooException Foo)//Not supported
// {
// }
catch(fox)
{
FooException::throwFooException(fox.Message,fox.StackTrace+funcvar);
//Info(tryvar);//Compile error tryvar is not declared because of try scope
}
catch(ex)//Different exception type than fox.
//same behaviour as catch(exception::error). Error exception type
{
info(ex.StackTrace);
}
//catch(Exception::Error)//Not supported. Same type as Catch(ex)
//{
// info(con2Str(xSession::xppCallStack(),','));
//}
Catch// Catch all
{
}
finally
{
//Clear memory and close connections if any
}
}
In the above code please note that we are throwing the exception again in the catch statement. This is done to capture the stack trace. We have to throw so that the stack trace can be helpful for consumption.
In the code above I have also added ErrorException type to show the difference and how it works along with other exception types.
Now we can catch FooException in the catch hierarchy as a new type like ErrorException.We should be able to build a list of business exception like FooException in the catch list like below
FooException1 foo1;
FooException2 foo2
Catch(foo1)
{
}
Catch(foo2)
{
}
With the above approach we have the specific type and the business message associated with the exception. There are lot more things we can do with this approach. We can add state to these exceptions and handle the exceptions in a different way based on state.
*This post is locked for comments