Creating oData actions and testing with Fiddler

I have been experimenting with oData actions and testing them using Fiddler. Overall it is easy to implement them and they can be used as a “cheap” alternative to custom services. Compared to a custom service, the Data Entity can act as the data contract and removes the need of querying/saving a complicated table structure.

There are two kind of actions you can add, collection level (static) actions and instance level actions. We are going to use the Fleet Management module in order to create one action of each type and then test it using Fiddler.

Collection level actions

First we will need to create a data entity for the table FMVehicle. Nothing special is need when creating the entity and we only need the FMVehicle data source for our example.

FMVehicleEntity data entity

The first method we will add will be a collection level i.e. static method. The AvailableVehicles method is querying the data entity and applying a complex filter to get a list of vehicles that will be available within the desired date interval.

    [SysODataActionAttribute("AvailableVehicles", false),
        SysODataCollectionAttribute("return", Types::Record, "FMVehicleEntity")]
    public static List AvailableVehicles(utcdatetime availableFrom, utcdatetime availableTo)
    {
        FMVehicleEntity vehicle;
        List vehicles;
        FMRental rental;
 
        vehicles = new List(Types::Record);
        while
            select vehicle
            notexists join rental
            where rental.Vehicle == vehicle.RecId
                && rental.StartDate <= availableTo && (rental.EndDate >= availableFrom
                    || rental.EndDate == DateTimeUtil::minValue())
                && rental.State != FMReservationState::New
                && rental.State != FMReservationState::Complete
        {
            vehicles.addEnd(Vehicle);
        }
 
        return vehicles;
    }

Notice the static modifier on the method declaration, it is needed for collection level oData actions. Also, all oData actions should be decorated using the SysODataActionAttribute attribute and since this is a class level method the second parameter should be set to false. In this case, we want to return a list and in a similar way as for custom services we need to use another attribute (SysODataCollectionAttribute) to define the type of the list elements.

Before diving into the details of testing it with Fiddler you might want to have a look at this blog post which explains the fundamentals. Calling oData actions is always done using a POST request even if they are not updating the data entity. The structure of the Fiddler request to call an oData action looks like:

POST https://[base url]/data/[entity resource name]/Microsoft.Dynamics.DataEntities.[action name] HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json;odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
Authorization: Bearer [access token]
Host: [base url]

{
    [JSON formatted method parameters]
}

And an example of calling the AvailableVehicles method is

POST https://[base url]/data/FleetVehicles/Microsoft.Dynamics.DataEntities.AvailableVehicles  HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json;odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
Authorization: Bearer [access token]
Host: [base url]

{
     "availableFrom": "2017-05-03T07:00:00.000Z",
     "availableTo": "2017-05-07T20:00:00.000Z"
}

Instance actions

An instance action, as you would expect, should be defined as a normal member method of the data entity i.e. not static. Calling an instance action requires that we first have a single data entity record to call it against, e.g. one vehicle. NextAvailableDate  is an example of such a method, it is used to calculate the next date a vehicle is available.

    [SysODataActionAttribute("NextAvailableDate", true)]
    public utcdatetime NextAvailableDate(utcdatetime afterDate)
    {
        FMRental rental;
 
        select maxOf(EndDate)
        from rental
        where rental.Vehicle == this.RecId
            && rental.State != FMReservationState::New
            && rental.State != FMReservationState::Complete
            && rental.StartDate >= afterDate;
 
        return rental.EndDate == DateTimeUtil::minValue() ? afterDate : rental.EndDate;
    }

Again, we should decorate the method with the SysODataActionAttribute attribute. The value of the second parameter should be true since we are implementing an instance action.

Testing it through fiddler requires that we combine two oData features, reading a record by primary key and calling an action for it. The request format of a query based on the primary key of the record is:

GET https://[base URL]/data//[entity resource name]([keyField1] = [value1],[keyField2] = [value2] …) HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
Authorization: Bearer [access token]
Host: [base URL]

An example of reading a vehicle by primary key is:

GET  https://[base URL]/data/FleetVehicles(VehicleId='Adatum_Four_1')  HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
Authorization: Bearer [access token]
Host: [base URL]

We will have to change somewhat the basic primary key oData query to call the action. Action calls should be done using POST requests. We also need to specify the method name and any parameters. The structure of the request looks like:

POST https://[base URL]/data//[entity resource name]([keyField1] = [value1],[keyfield2] = [value2] …)/Microsoft.Dynamics.DataEntities.[action name] HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json;odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
Authorization: Bearer [access token]
Host: [base URL]

{
    [JSON formatted method parameters]
}

And an example of invoking the NextAvailableDate action is

POST  https://[base URL]/data/FleetVehicles(VehicleId='Adatum_Four_1')/Microsoft.Dynamics.DataEntities.NextAvailableDate  HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json;odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
Authorization: Bearer [access token]
Host: [base URL]

{
    "afterDate" : "2017-05-05T10:00:00.000Z"
}

 

Troubleshooting

oData responses can sometimes be misleading. The following is a list of errors and the corresponding oData behaviours which might help you quickly identify the problem:

  • Failing to define a collection action as static or defining an instance action as static will have the effect that AX compiler will silently ignore the action and not include it in the oData metadata.
  • When querying using a primary key if you do not specify all key fields correctly, oData will respond with the not so helpful message “No HTTP resource was found that matches the request URI”.
  • When calling oData actions if you don’t specify the correct number of parameters and appropriate values for them, oData will respond with an exception mentioning “InvokeAction – Model state is invalid.”
  • If you try to define an optional parameter in the AX method that implements the oData action, when calling it through oData you will get a message mentioning “Ambiguous match found.”

Sources

One thought on “Creating oData actions and testing with Fiddler

  1. Thank you very much for this post. Since the resource for D365FO is limited the post like this will be very helpful for new D365FO developer like myself.

    I have a question regarding on the action in the data entity. Is it possible to post an object instead of primitive types? I have tested this and works if the post body is string or integer,… however i wasn’t able to post an object as json or stringify json (the following is the example of json object i want to send to action)

    var test = {
    “param”: {
    “Name”: “John Done”,
    “SchedRsrcType”: “SchedRsrcType”
    }
    };

Leave a Reply

Your email address will not be published. Required fields are marked *