A while ago I wrote about our Extension Design Principles. In it I discussed various ways to interface between extensions without having to take dependencies. One of these interface methods was the workflows Business Central is equipped with.
Because you can configure these workflows at run-time you can configure Workflow Events and responses from different extensions.
But how can you create new workflow events and responses? Quite easily in fact. I suggest you start by reading how you can configure existing Workflows by reading this excellent piece by ArcherPoint. That will give you a good idea about what you can do with workflows.
In the aforementioned Extension Design Principles I also spoke about how we want to move our email address validation to an API. As I said, we want a consistent check across all our ERP systems, web shops, and other platforms. But we don’t want to program a call to this API in all our extensions. And we don’t want our extensions to have to take a dependency on a base extension or something. As I said, we want to be able to have all our extensions as stand alone units in the App Source.
How to do it then? With a workflow of course. To the code!
Create the check email Azure Function
First thing I did was create an Azure Function for our email address check. As you can see in the code below it checks the formatting of the email address first and, if that is correct, checks to see if the email addresses hostname has an MX record. So a bit more reliable than the CheckValidEmailAddress function in CodeUnit 9520.
The DNS check is done with an online API. If you would consider taking something like this to production you should build or host something like it yourself. Or just use a DNS check library. Also, bear in mind that a domain does not need to have a MX record in order to be email enabled.
For the curious, you can read how to create an Azure Function in VS Code here.
After this I created a CodeUnit that calls this Azure Function. In it I work with Variants. As I said, this function needs to be callable from many different extensions and many different source records. This function is quite simple, just a simple call to the Azure Function. If it gets a response code 200 it shows a message saying the email address is correct. If the response code is 400 it throws an error.
Create the workflow response
Then of course the real issue of this blog post, how do I call this function from the workflow? For this you need to create a workflow response. Have a look in CodeUnit 1521. If you follow the code in there you see you need to do a few things to add a workflow response:
- Create a function to add a response to the library;
- Call that function from the event that is triggered when workflows are added to the library;
- Call the response from the workflow using the event that is triggered when a workflow runs.
As you can see in the above image the steps are quite simple. A few highlights.
In the call to the WorkflowResponseHandling.AddResponseToLibrary you can see me use ‘GROUP 6’. These groups are used to add arguments to your workflow step. GROUP 6 adds a Field No argument. This is used to identify the email address field on the triggering record. Other groups are found on Page 1523:
- ‘GROUP 0’: No Arguments
- ‘GROUP 1’: Selects General Journal Template and Batch
- ‘GROUP 2’: Selects the linked target page to open
- ‘GROUP 3’: Create Notification
- ‘GROUP 4’: Create Message
- ‘GROUP 5’: Selects approval arguments
- ‘GROUP 6’: Selects field
- ‘GROUP 7’: Applies selected values to selected field
- ‘GROUP 8’: Selects Response Type and User ID”
I have added a function to delete the added workflow responses from the library. This should be called on uninstallation of the extension. Unfortunately this is not yet possible with installer CodeUnits.
Once we have added the response to the library we can create an event subscriber that will run our code.
What happens here is really quite simple. The code gets the workflow response and its arguments. If the Function Name of the workflow response is one of ours we execute the code that belongs to it. The Variant and xVariant come from the workflow event and represent the record that triggered the workflow. As you see I use the Workflow Step Argument to get the Field No. that we want to use.
If the workflow response has executed, the code needs to switch the ResponseExecuted variable to true. This lets the event publisher know that the event has been dealt with.
Creating the workflow event
On the events side things work more or less the same way. First we create an event that can trigger the workflow. This particular event is added in an extension that adds a notification email address on the Job Queue Entry. Once we have created the event all we need to do is make sure that the OnValidate event on our new field triggers the workflow:
Configuring the workflow
Now we have created the workflow event and response we can configure them. The events that add the event and response to their libraries is triggered when you open the Workflows page. Nothing needs to be done for that.
Just create a new workflow. Set the event to the newly created event. Set the response to the new check email response. Just make sure that you select the email address field in the Workflow Responses.
As a bonus you can even use your response with the standard Dynamics events. As you can see in the image below the workflow trigger I use here is the change on the customer record. Unfortunately the standard language of my Business Central sandbox is Dutch so all the events are added in that language. Not a problem for most users but still something I feel Microsoft should address. As you can easily regenerate all the events and responses by clearing tables 1520 and 1521 and opening the workflows page it is not a big problem.
That is pretty much it. I’m sure we can all delve deeper into the workflows as I have not yet touched upon workflow templates of event predecessors yet. That is something for another day.
For now I want to urge you all to explore the workflow module and start adding events and responses to your extensions. That will make my job of integrating with them much easier.
As always, all the code is on GitHub. Happy coding!