When writing workflow plugins for Dynamics365 it seems that standard practice if an exception or error appears in the code is to throw an exception which will pop up on the interface for the user, or appear in the logs if it isn’t a real time workflow.
The problem with this approach is that when the exception message is shown it can be a little scary for users as often the text is taken directly from the Exception itself. This means that users see generic system error messages like “Null Expection Value” instead of the more elegant “An error has occurred”, or something more meaningful to the user.
I would propose a simple change of adding two extra output fields to the plugin, on a simple yes/no bool field called “Success”, and the other a text field called “ErrorText”,
[Output("Success")]
public OutArgument<bool> Success {get;set;}
[Output("ErrorText")]
public OutArgument<string> ErrorText {get;set;}
In the action code when an exception is thrown and caught within the plugin, instead of throwing an InvalidPluginExecutionException exception directly with the error text, the “Success” field should be set to false and the “ErrorText” field can be set to the error message before the plugin terminates.
protected override void Execute(CodeActivityContext ActivityContext)
{
try
{
// do stuff here
Success.Set(ActivityContext,true);
}
catch(Exception ex)
{
Success.Set(ActivityContext,false);
ErrorText.Set(ActivityContext,ex.message);
}
}
Inside the plugin as there is a single main catch() block, then validation errors etc can throw an Exception which will be caught and passed out of the plugin and back to the workflow. The user can then decide how these get handled.
So instead of the plugin throwing the exception and stopping the workflow, the workflow checks for the status of the “Success” field and stops the workflow. This enables errors to be handled more gracefully than just crashing out of the workflow process.
Setting the stop workflow step to “Canceled” for a real-time workflow will cause the transactions to be rolled back in the same way that throwing an exception inside a plugin would.
This is probably most useful when writing plugins that may be used more than once to provide additional flexibility.
A possible extension to this may be to include another input field as a simple bool called “DoNotThrowExceptions”. This can be set with a default value of false so that the plugin behaves as a normal plugin with exceptions being thrown, or if the creator of the workflow sets it to true then the results will be returned via the “Success” and “ErrorText” fields.
protected override void Execute(CodeActivityContext ActivityContext)
{
try
{
// do stuff
// or throw an explicit exception, perhaps for validation
if(myval!=expected) {
throw new Exception("validation error - field1"); }
Success.Set(ActivityContext,true);
}
catch(Exception ex)
{
Success.Set(ActivityContext,false);
ErrorText.Set(ActivityContext,ex.message);
if(!DoNotThrowExceptions){
throw new InvalidPluginExecutionException(ex.message);
}
}
}
[Input("Do Not Throw Exceptions")
[Default("false")]
public InArgument<bool> DoNotThrowExceptions {get;set;}
[Output("Success")]
public OutArgument<bool> Success {get;set;}
[Output("ErrorText")]
public OutArgument<string> ErrorText {get;set;}
This way the plugin will behave normally and throw exceptions, but if you need a scenario where more control is required then the “Do Not Throw Exceptions” field can be set to true in the calling workflow step and the results will be returned to the workflow instead.
Having a single exception handler means that both runtime errors and also data validation errors can be handled with exceptions and they are all returned to the workflow. Additional logic could be included especially if legacy Dialogs (to be depreciated) are being used to provide field level validation.
Going to give this idea a go, have never liked the generic exception method of returning errors or even simple messages back to the user from a plugin. So simple.
Interesting idea, especially if you have both methods for just a few extra lines of code. I might start doing this.