I’ve promised this post to some attendees of my last Dynamics 365 Business Central development workshop in Microsoft Italy (c/o Microsoft House) last week.

Question was: How can I call Dynamics 365 Business Central logic from an external application? Simple answer given by all: you can publish a codeunit as web service and use the SOAP endpoint.

But if I want to use OData? You cannot publish a codeunit as an OData endpoint. Answer: you can call custom Dynamics 365 Business Central functions via ODataV4 by using Bound Actions. Bound Actions are actions related to an entity that you can call via an HTTP call and they can invoke your business logic (functions).

Unfortunately, documentation about how to use OData V4 bound actions with Dynamics 365 Business Central is quite poor and with this post I would like to help clearing this topic a bit more. There are two main scenarios that I want to cover here:

  • Calling a D365BC codeunit that performs business logic, like creating new entities
  • Calling a procedure in a D365BC codeunit by passing parameters and reading a return value

For this sample I’ve decided to create an extension with a codeunit that contains the business logic that I want to call via OData. My codeunit has two business functions:

  • CloneCustomer: it creates a new customer based on an existing customer record
  • GetSalesAmount: it gives the total sales amount for a given customer

The codeunit code is defined as follows:

ODataBoundActions_01

To use OData V4 bound actions you need to declare a function in a page and this function must have the [ServiceEnabled] attribute.

For this demo project, I would like to publish the Customer Card (page 21) as OData web service, so the natural thing to do is to create a pageextension object of the Customer Card to add our [ServiceEnabled] procedure and then publishing the Customer Card as web service. If you try to do this, it will never work!

If you declare a [ServiceEnabled] function in a pageextension object and you try to reach the metadata of the OData endpoint (baseurl/ODataV4/$metadata), you will not see the action published.

To publish your action attached to the Customer entity, you need to create a new page like the following and then publishing it as web service:

ODataBoundActions_02

Here, ODataKeyFields property specify what is the field to use as key when calling the OData endpoint (I want the “No.” field of the Customer record).

Inside this page, I declare two procedures to call the two methods defined above in our AL codeunit:

ODataBoundActions_03

Here:

  • CloneCustomer is a procedure called without parameters. It takes the context of the call and calls the CloneCustomer method defined in our codeunit.
  • GetSalesAmount is a procedure that takes a Code parameter, calls the GetSalesAmount procedure defined in our codeunit and returns the result as response.

What happens with the following definitions when we publish the MyCustomerCard page as web service (here called MyCustomerCardWS)?

If we reach the OData V4 metadata, we can see that now we have the actions published:

ODataBoundActions_04

Now we can try to call our bound actions via OData. As a first step, we want to call the CloneCustomer function. For this, we need to send a POST request to the following endpoint:

https://yourbaseurl/ODataV4/Company('CRONUS%20IT')/MyCustomerCardWS('10000')/NAV.CloneCustomer

I’m using the REST Client extension to send HTTP requests to the above endpoint. This is the request sent:

ODataBoundActions_05

and this is the result of this call:

ODataBoundActions_06

What happens on Dynamics 365 Business Central? The code in our codeunit is called and we have a Customer record created (cloned by the customer with “No.” = 10000 as the input):

ODataBoundActions_07.jpg

Our second function to call (GetSalesAmount) wants a Code[20] parameter as input (not needed but it’s only to show hot to pass parameters to a bound action). We need to send a POST request to the following endpoint:

https://yourbaseurl/ODataV4/Company('CRONUS%20IT')/MyCustomerCardWS('10000')/NAV.GetSalesAmount

by passing a JSON body with the parameters (name and value).

This is the request sent:

ODataBoundActions_08

and this is the response:

ODataBoundActions_09

We have the value of the total sales amount for the given customer (retrieved by calling our codeunit method).

Here there’s a point to remember, because it was for me a source of hours spent on debugging: parameter’s name to pass on the JSON object must match with the OData metadata, not with your function’s parameters.

For example, if you declare the bound action as follows:

ODataBoundActions_10

where CustomerNo parameter has capital letters, the OData metadata is as follows:

ODataBoundActions_11

so the JSON must be passed accordingly (parameters names must match).

Not so easy, but very powerful when you understand the logic