Since Dynamics CRM 2013 we’ve been able to create our own APIs within the XRM framework using the Custom Action process type. Recently a new option appeared in the maker portal called Custom APIs which sounded very similar, so I thought I’d take a look.
The old: Custom Actions
Custom Actions could be “bound”, which linked them to a particular entity type, or “unbound”, which was a global action. Either type could have additional input or output parameters, and the core operation of the action was defined by the same workflow engine that the standard background workflows used.
Possibly the most important impact of custom actions was that they introduced a new message type that functioned exactly like the standard actions we were used to calling, like Update
or QualifyLead
. This made them instantly accessible to existing custom Javascript or SDK apps, but also to plugins. Developers could attach plugins to these custom action messages, retrieve the input parameters and set the output parameters.
The transition: Flow
In Unified Interface, references to workflow essentially vanished as we were directed towards Power Automate Flows for automation. However, there was no other replacement for custom actions, the core of which were still defined by the old workflow engine. Change had to come.
The new: Custom APIs
I haven’t seen any documentation yet about custom APIs, but it appears to be a very similar setup to custom actions. They appear in the maker portal in the list of objects you can create in a solution.
From the maker portal we can create a new Custom API object and fill out some key details:
- The
Unique Name
– this is the name that will be used for the CDS message to call the API Name
,Display Name
andDescription
– all for reference purposes only as far as I can tellBinding Type
andBound Entity Logical Name
– similar to custom actions, you can select for the API to be global or bound to a specific entity type. Unlike custom actions you can also choose for the action to be bound to an entity collection, more details on this laterIs Function
– if this is “No”, the API can be called using aPOST
Web API request. If it’s “Yes”, it needs to be called with aGET
request. Either can also be called via the SDK using anOrganizationRequest
.Allowed Custom Processing Step Type
– this allows you to restrict what plugin steps can be added to your new message – either none, async-only or both sync and async.Execute Privilege Name
– use this to limit who can call your API based on security roles, e.g. “prvCreateAccount” to allow only users that are allowed to create accounts to call the APIPlugin Type
– defines the plugin that forms the core execution step of this message
Once the Custom API record is created you can add request and response parameters too – each of them requires the unique name that is used to refer to it in code, and the type of value it will hold.
Global & Bound APIs
A global custom action or custom API is one that is not called in the context of any other record. For example, the standard WhoAmI
message is entirely standalone. Global actions can take input parameters to give details of records to work on though.
From Web API, a global action is called by making a request to /api/data/v9.0/name. From an SDK app you create and execute a custom OrganizationRequest
, giving the name of the action as the message name in the constructor:
var req = new OrganizationRequest("mcd_ValidateAccountReference") { ["AccountReference"] = "ACC001" }; var resp = svc.Execute(req);
A bound action runs in the context of a particular record of a given type. If you create a custom action or API that is bound to accounts, you could call it via Web API using /api/data/v9.0/accounts(guid)/Microsoft.Dynamics.CRM.name. An SDK app would use the same pattern as for a global action, but supply the reference to the account as the Target
parameter:
var req = new OrganizationRequest("mcd_ValidateAccountReference") { ["Target"] = new EntityReference("account", new Guid("182580da-7ccc-e911-a813-000d3a7ed5a2")), ["AccountReference"] = "ACC001" }; var resp = svc.Execute(req);
Custom APIs introduce the idea of an API that is bound not to a specific entity, but to an entity collection. This gives the API a query that defines the records it should run on. I haven’t yet managed to invoke this type of API via the Web API, but from an SDK app it looks like:
var req = new OrganizationRequest("mcd_ValidateAccountReference") { ["InputCollection"] = new QueryExpression("account") { Criteria = new FilterExpression { Conditions = { new ConditionExpression("name", ConditionOperator.Equal, "Data8") } } }, ["AccountReference"] = "ACC001" }; var resp = svc.Execute(req);
Whether it’s bound to a specific entity or a collection, the framework validates that the Target
or InputCollection
parameters are valid based on the entity type that the API is bound to, so it’s not possible to accidentally call the API with the wrong type.
Example
I’ve created a Custom API called mcd_ParseInt
as shown:
with one request parameter:
and one response parameter:
The plugin that is referenced by the API is a very simple class:
public class CustomApiPlugin : IPlugin { public void Execute(IServiceProvider provider) { var context = (IPluginExecutionContext) provider.GetService(typeof(IPluginExecutionContext)); context.OutputParameters["IntOutput"] = Int32.Parse((string) context.InputParameters["StringInput"]); } }
I can execute my new API via C#:
var req = new OrganizationRequest("mcd_ParseInt") { ["StringInput"] = "123" }; var resp = svc.Execute(req); var result = (int) resp["IntOutput"];
Comparison
So how do Custom APIs stack up against Custom Actions?
Custom Action | Custom API | |
Solution Aware | ✔ | ✔ |
Input Parameters | ✔ | ✔ |
Output Parameters | ✔ | ✔ |
Can attach plugins | ✔ | ✔ |
Core logic definition | Workflow | Plugin |
Global actions | ✔ | ✔ |
Bound actions | ✔ | ✔ |
Collection actions | ✔ | |
Control over 3rd party plugins | ✔ | |
Built-in security | ✔ | |
Handles changing parameter definitions | ✔ |
The four differences at the end could be quite significant in particular use cases:
- we’ve never been able to pass a query to a custom action before – you could have created a string parameter that you put FetchXML into, but this gives callers the choice of what query type to provide
- being able to control whether third parties can attach plugins to your new message, or whether they can do so synchronously or asynchronously, could be of particular benefit to ISVs looking to limit further customization of key parts of their solutions
- having a built-in way to control who can access the API via the standard CDS privilege system moves a task that would normally be done in the associated plugin into a declarative configuration setting, making it easier and less error prone to implement
- errors encountered when developing a new custom action caused by adding or removing parameters was a common blocker to development. This restriction appears to be gone with custom APIs, making it much easier to iterate and refine the definition of APIs early in the development process
Very nice and thorough article! I’m a bit surprised to see that in this no code/low code era, custom API’s seem to be more code oriented than their Custom Action counterpart. A lot of Custom Actions were really no code solutions because of the workflow designer behind it… Also, it looks like they are not directly executable from within a business process flow, right?