JSON-based custom service with parameters (AX 7)

Dynamics 365 for Operations deploys custom web services in two ways: as SOAP-based services and JSON-based services. AX developers are often familiar with SOAP services (which were used in AX 2012), but JSON-based ones are new to them.

One particular challenge is passing arguments to a service. Let’s say we have an Add operation, which can sum two numbers.

public int Add(int a, int b)
{
    return a + b;
}

To call the service, we have to provide values for both arguments, a and b. Because the expected format is JSON (Java Script Object Notation), we have to provide a JSON string describing an object with two properties (a and b) and their values. This is it:

{ a: 2, b: 5 }

Note that names of the properties are important – they must match parameter names in the X++ method.

Because building JSON strings by yourself can be cumbersome (especially with more complex parameters), a better approach is working with objects and leaving conversion to JSON to a serializer.

For example, you can build a simple class,

public class AddContract
{
    public int a { get; set; }
    public int b { get; set; }
}

create an instance with required values and call JsonConvert (from Newtonsoft.Json) to convert it to string:

AddContract contract = new AddContract { a = 2, b = 5 };
string json = JsonConvert.SerializeObject(contract)

If you need AddContract class just at this single place, maybe it’s not worth creating it at all. We can use an anonymous object instead and it will still work the same.

var contract = new { a = 2, b = 5 }; // Anonymous object
string json = JsonConvert.SerializeObject(contract)

When we have the JSON string, we have to send it to the service. The implementation below assumes that you use AuthenticationUtility from the sample solution from Microsoft.

// Prepare HTTP client, including authentication
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(ClientConfiguration.Default.UriString);
client.DefaultRequestHeaders.Add(OAuthHelper.OAuthHeader, OAuthHelper.GetAuthenticationHeader());
 
// Define parameters
var contract = new { a = 2, b = 5 };
 
// Create a request
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
                                                    "api/services/MyServiceGroup/MyService/Add");
request.Content = new StringContent(JsonConvert.SerializeObject(contract),
                                    Encoding.UTF8,
                                    "application/json");
 
// Run the service
var result = client.SendAsync(request).Result;
 
// Display result to console
if (result.IsSuccessStatusCode)
{
    Console.WriteLine(result.Content.ReadAsStringAsync().Result);
}
else
{
    Console.WriteLine(result.StatusCode);
}

If you’re using WebRequest instead of HttpClient (as in Microsoft sample code), you can use add parameters to the request in this way:

var contract = new { a = 2, b = 5 };
string json = JsonConvert.SerializeObject(contract);
 
Byte[] byteArray = Encoding.UTF8.GetBytes(json);
 
request.ContentLength = byteArray.Length;
request.ContentType = "application/json";
 
using (Stream dataStream = request.GetRequestStream())
{
    dataStream.Write(byteArray, 0, byteArray.Length);
}

It’s clearly nothing difficult, just make sure that your properties in JSON exactly match parameter names. For example, if parameters of my Add operation were prefixed with underscore (as usual in X++), the JSON string would have to be { _a: 2, _b: 5 }.

20 Comments

  1. what are the options on AX side to write these custom services, X++ operations are the only way to do that?

  2. Hi Martin,

    Just wanted to thank you for this post, helped me quite a lot.
    I use classes as request & response objects which complicates the matter a little bit.
    But my easy workaround was to add the soap wsdl (via add web/service reference).
    all my request & response objects are then available and I can serialize/deserialize easily.

    I switched to JSON because I’m unable to get the SOAP webservice to work on a mono .Net (Xamarin.Android). Already posted my issue on several forums, still curious why this isn’t working properly.

    You’re a great contributor to the D365 world! Keep up the good work …

    • Hi Sven, do you have an example that you can share with Request/Response objects? I’m in the same scenario and trying to figure out how to build the request body. Thanks!

  3. Thanks Martin,
    But I need your support in posting json array.
    Because it gives this error:
    “Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray.”
    Are there a specific format , or JsonObject that contain the array as a parameter?
    Your support is highly appreciated

    • Martin Dráb

      The Real Person!

      Author Martin Dráb acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.

      Can you please give me more details about your particular scenario?
      Note that a discussion forum would be a better place for asking such questions, there are more people to help you.

      • In your above example, you are sending a single object of type contract.
        So, what if we want to send many contracts objects in the same post request?
        I tried to send json array like : [{ a : 2, b : 5 },{ a : 1, b : 3 },{ a : 9, b : 4 }]
        But, the server respond with this error:
        “Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray.”

        • Martin Dráb

          The Real Person!

          Author Martin Dráb acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.

          I think you’re forgetting to identify the parameter.
          If your parameter is int x, JSON will be { x: 2 }. If your parameter is List x, JSON will be { x: [ … ] }.
          Therefore your actual JSON will be something like { startArray: [{ a : 2, b : 5 },{ a : 1, b : 3 },{ a : 9, b : 4 }]}.

  4. Hi Martin,
    I was able to follow your steps and i was able to get the Deserialized object back to AX.
    But the challenge is inserting the the data from “DataContract Class object” into a table.
    At the moment i can only read the 1st record. Which implies i need to do some kind of loop on the Data Contract.
    Please assist.

    • Martin Dráb

      The Real Person!

      Author Martin Dráb acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.

      Let me use AddContract from my blog post as an example.
      An instance of AddContract above can hold only one set of values (‘a’ and ‘b’). If you need several instances, you need a collection. You would declare the parameter in AX as List, decorated the method with AifCollectionTypeAttribute, and you would use an array in JSON. In AX, you would use a ListEnumerator to iterate the collection.
      This isn’t a good place for extended discussion, sharing code samples and so on. Please go to Dynamics 365 for Finance and Operations Forum if you need more help. You’ll also reach more people there, not just myself.

  5. How did you call the add method using this var result = client.SendAsync(request).Result?
    you didn’t specify what method you want to call, what if there was more than one method in one service?
    and if you want to use soap endpoint, then you’ll do the same but different c# code structure?

    • Martin Dráb

      The Real Person!

      Author Martin Dráb acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.

      Look at how I create ‘request’. There I specify (using an URL) that I want to call ‘Add’ operation of a web service ‘MyService’.
      I’m not sure what you mean by “the same but different c# code”. It would be very different.

  6. The code you wrote here:
    var contract = new { a = 2, b = 5 };
    string json = JsonConvert.SerializeObject(contract);

    Byte[] byteArray = Encoding.UTF8.GetBytes(json);

    request.ContentLength = byteArray.Length;
    request.ContentType = “application/json”;

    using (Stream dataStream = request.GetRequestStream())
    {
    dataStream.Write(byteArray, 0, byteArray.Length);
    }

    It should be written instead of what lines in the above code? what should i keep and what should i delete?

    can we use the code here: https://github.com/microsoft/Dynamics-AX-Integration/blob/master/ServiceSamples/JsonConsoleApplication/Program.cs ? here i think they used web request but didn’t use UTF8 like you?

    How did you know that the expected is JSON, json based services work with XML as well right?

    last question, i saw the code on getHub for soap and json but i can’t see the difference why we had to do this in soap and why we had to do this in json?

    • Martin Dráb

      The Real Person!

      Author Martin Dráb acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.

      No, JSON-based services don’t use XML format. They’re called JSON-based because they use JSON format.
      The code sample doesn’t “use UTF-8” because it doesn’t do what this blog post is about – it doesn’t pass any parameters to the service.
      I know you’re aware of the Dynamics Community forum, because I already replied to some of your questions there. Please use the forum for general discussion and here put just reactions to this particular blog post.

  7. Martin, thanks for explanation! You also can make a youtube video based on your material and get subscribers from https://soclikes.com/ for your youtube channel to help more people.

  8. Hi Martin,

    I am trying the POST request for creating a sales order but is failing with error “System.Net.WebException: The remote server returned an error: (500) Internal Server Error.” No authorization issue as token is getting generated successfully.

    Error messages from Fiddler:
    1. By passing the sales data contract as input after initializing the data contract with required values:

    ExceptionType=XppServicesDeserializationException
    Message=An exception occured when deserializing a parameters – Exception occurred when parsing and deserializing parameter ‘hsSalesTableContract’ – ‘Parameter ‘hsSalesTableContract’ is not found within the request content body.’

    2. By passing the JSON file as input to the service:

    ExceptionType=XppServicesDeserializationException
    Message=An exception occured when deserializing a parameters – Exception occurred when parsing and deserializing parameter ‘hsSalesTableContract’ – ‘Error while deserializing contract class’

    Not getting what am I missing here. Your help is highly appreciated. Stuck with

  9. Hi Martin,

    I am trying the POST request for creating a sales order but is failing with error “System.Net.WebException: The remote server returned an error: (500) Internal Server Error.” No authorization issue as token is getting generated successfully.

    Error messages from Fiddler:
    1. By passing the sales data contract as input after initializing the data contract with required values:

    ExceptionType=XppServicesDeserializationException
    Message=An exception occured when deserializing a parameters – Exception occurred when parsing and deserializing parameter ‘hsSalesTableContract’ – ‘Parameter ‘hsSalesTableContract’ is not found within the request content body.’

    2. By passing the JSON file as input to the service:

    ExceptionType=XppServicesDeserializationException
    Message=An exception occured when deserializing a parameters – Exception occurred when parsing and deserializing parameter ‘hsSalesTableContract’ – ‘Error while deserializing contract class’

    Not getting what am I missing here. Your help is highly appreciated. Stuck with this since a week. Tried both HttpClient and WebRequest.

  10. If we make the second parameter optional
    public int Add(int a, int b = 0)
    {
    return a + b;
    }
    and call the method by passing only the first one
    { a: 2}
    this raises the exception “An exception occured when deserializing a parameters…”
    is there a workaround for this?

  11. Hi Martin, do you have an idea how to specify a company when communicating with the JSON service? Like if I want to retrieve data from any of my specified JSON service operations, which company is used to get that data? Do I need to specify the company as parameter and handle it in D365 / X++?

Comments are closed.