Dynamics 365 developers have been using Custom Process Actions to call C# code for years. Now with Custom APIs we can skip the Action scaffolding and wire up our code more directly. Should you upgrade your Actions to APIs? We’re going to take a look at the performance and see how that factors in.
This second method is how I always implemented it (because that’s how I learned to do it) and that’s what we’re looking at today. The basic setup is like this:
- Write the C# code as a Custom Workflow Activity (CWA)
- Create an Action type process to call the CWA
- Create Action parameters for input and output values
- When you call the CWA, pass through the Action input values
- Assign output values from the CWA to the Action’s output parameters
- Call the action from the form and interpret the results
Microsoft recently announced the availability of Custom APIs, which is a way to create your own messages in Dynamics 365 / Dataverse and have it call your plugin code directly as part of the Main Operation. With a Custom API you can define your input and output parameters and the plugin to call and the parameters are all passed through automatically.
Custom Process Actions have other strengths (see ‘Compare Custom Process Action and Custom API’ link below), but in a case where we just want to run code with parameters, do you get better performance from Custom APIs, and if so, should you convert you older Actions to Custom APIs?
With this benchmark test I wanted to test a few things:
- Since we don’t have to assign input and output parameters in the action, is calling a Custom API faster than calling an identical Custom Process Action?
- Does the number of parameters impact performance?
- Does the performance difference change if the messages are called from the Dynamics environment or a client machine?
Note: All the code and solutions needed to perform this test are available on GitHub at https://github.com/akaskela/Performance-Benchmarks
There are 3 different Custom APIs:
- One Input: Baseline API has one input and one output parameter. This API calls a plugin which reads from the input parameter and writes to the output parameter.
- Many Input: This API is identical to above but it has 10 additional input and output parameters. The code reads from each input parameter and writes it to the corresponding output parameter.
- Lots of Input: This is identical to above but increases the parameter count to 25 input and 25 output.
To compare those values, I created three CWAs that mirror those plugins (Baseline, Baseline + 10, Baseline + 25), and invoke those CWA from three Custom Actions. These Actions have additional steps to assign the input and output parameters, and if there is a performance difference this is where we’d expect to see it.
There is a ‘Benchmark Initiator’ entity with a text field for the name of the custom message to invoke, and the number of times to call that action. The Benchmark Initiator will then call the action with all input variables set, and return the time for each individual action call.
To perform the tests I repeated these actions 100 times:
- Loop over each of the 6 actions
- Create a Benchmark Initiator record for the specific action, telling it to run the message 25 times
- Store the 25 results as individual times executed from a plugin
- Repeat 25 times
- Call the same action locally and store the individual time as executed locally.
By testing the methods round-robin and alternating between Plugin and Locally we’re able to spread any network or noisy-neighbor scenarios evenly across the different scenarios.
The benchmark tests resulted in 30,000 times across 12 scenarios (3 CWA and 3 Custom API, locally and remotely). From these scenarios I took the median time to reduce the influence of extreme outliers.
The first two questions – are Custom APIs faster than a CWA, and does the number of parameters affect performance? In the time comparison below we can see the time difference when the call is made from a plugins (to eliminate network traffic):
At the baseline point with no additional parameters, the difference is negligible. On it’s own, an Action calling a CWA performs comparable to a Custom API.
However, as we ramp up the number of parameters we can see a disparity grow between the CWA and Plugin. When we get to 25 parameters, the CWA calls take 35% longer than the comparable Custom API calls. The scaffolding around setting the parameters in the CWA has a significant impact on the overall time of the operation.
Finally, discounting network speed, is there a difference between calling the Custom API or Action locally, compared to the plugin? Let’s take a look at our last chart to see how they stack up.
In this chart we see Local messages charted along the X-Axis on the left, overlaid with the Plugin messages charted along the right. There’s a pretty consistent difference of ~131ms between calling the message locally vs from a plugin. When we exclude the network constant across all numbers, the results are nearly identical. We can see from this that the number of parameters makes no difference with respect to calling the message locally vs from a plugin.
Should you replace your Custom Process Actions with Custom APIs?
If you have scenarios where you’re using CWAs, especially if there’s a large number of parameters, you should absolutely considering converting those to Custom APIs. What’s involved? First, you’ll need to update your code to convert it from a CodeActivity to an IPlugin. This really is pretty easy as you just need to change a few lines of setup and then how you’re getting and setting the parameters. Next, you’ll need to create a Custom API along with Custom Request and Custom Responses that correspond with each of your action parameters.
Fortunately, Business Applications MVP Mark Carrington developed a tool for the XrmToolBox (links below) that will help convert your Custom Processes to Custom APIs. This tool will create the API records you need based on the current Action and all you’ll need to do is associate it with your newly updated plugin. Major warning with this tool though – it will delete your existing Action and replace it with a new one with the same name. That makes it a seamless change as far as anything which calls the method, but deleting anything carries the possibility of data loss or rollout complications.
What if you don’t have a lot of parameters, is it still worth it? Not if you’re only picking up 1ms per operation. But if you look beyond the performance and look at it as a way to replace older processes (that will be deprecated at some point) with the “latest and greatest”, then it’s a discussion worth having for your specific situation.
Additional Testing Notes
All testing code and solutions are available on GitHub to reproduce my work (https://github.com/akaskela/Performance-Benchmarks).
The computer I performed the Local tests on were running on a fiber connection and at the time a Google speed test evaluated the connection at 401mb down / 448mb up with 4ms latency.
Additional testing can be performed to evaluate Custom Process Actions which use code as a plugin instead of a CWA to see if the difference holds in that case. Additionally, since the Output steps require additional steps in the Action, testing can compare 25 input parameters vs 25 input and 25 output parameters to see if those extra steps are the source of the difference.
Microsoft Docs: Create and use Custom APIs: https://docs.microsoft.com/en-us/powerapps/developer/data-platform/custom-api
Custom Action to Custom API Converter: https://www.xrmtoolbox.com/plugins/MarkMpn.CustomActionToApiConverter/
Compare Custom Process Action and Custom API: https://docs.microsoft.com/en-us/powerapps/developer/data-platform/custom-actions#compare-custom-process-action-and-custom-api