Calling GraphQL queries in Dynamics NAV using RESTSharp

In my last project I had, once again, the task to develop an external webservice connection in NAV 2016 (C/AL). The goal was to fetch orders from an API in dynamics nav and write back some states. In itself this is of course a simple story. But in this case, I came across GraphQL for the first time. GraphQL is a query language for interfaces (which was originally developed by facebook).

You can use these queries to define exactly what you can get back. So, you pick the answers yourself, so to speak.

Be aware: As C/AL is unable to identify the correct “Execute()” overload in RESTSharp, you must create a wrapper-dll!
In Business Central, you should, for sure, use the new AL HttpClient types.

Example GraphQL query

A simple example from GraphQL | A query language for your API is:

{
  hero {
    name
    height
    mass
  }
}

And this json-like query returns the following, self-defined data…

{
  "name": "Lukas Skywalker",
  "height": 1.72,
  "mass": 77
}

Additionally, you could work with variables inside your queries, connect them and far more… In this case, if you would like to query heroes, but you’re only interested in Jedis and their friends:

query HeroNameAndFriends($herotype: Herotype) {
  hero(herotype: $herotype) {
    name
    friends {
      name
    }
  }
}

The variable $herotype will then be set and send as an own json representation:

{
  "herotype": "JEDI"
}

The combined query and variables will finally look like this in a request-body:

{
   "query":"query HeroNameAndFriends($herotype: Herotype) { hero(herotype: $herotype) { name friends { name } }}",
   "variables":{
      "herotype":"JEDI"
   }
}

If everything goes well, you will receive the following filtered result:

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

There’s plenty more possibilities with GraphQL, but I don’t want to dive too deep into this topic. So, I just start sharing my little pattern to call these queries from Dynamics NAV here. In my case I use RESTSharp, the well-known http-client library.

For sure, this should be usable with the WebClient class or Business Central with its new HttpClient types as well.

The GraphQL wrapper

In this wrapper function we construct and execute the actual request

LOCAL [TryFunction] ExecuteQuery_GraphQL(Query : Text;Variables : Text;VAR IRestResponse : DotNet "RestSharp.IRestResponse")

//RestClient Instance
CreateRestClient(RestClient, RestRequest, RestMethod.POST);

//Add general headers
IF ISNULL(AdditionalHeaders) THEN
  CreateDictionary(AdditionalHeaders);

AdditionalHeaders.Add('Accept', '*/*');
AdditionalHeaders.Add('Accept-Encoding', 'gzip, deflate, br');
AdditionalHeaders.Add('Connection', 'keep-alive');
AdditionalHeaders.Add('Content-Type', 'application/json');

//Get additional headers
FOREACH AdditionalHeader IN AdditionalHeaders DO BEGIN
  RestRequest.AddHeader(AdditionalHeader.Key, AdditionalHeader.Value);
END;

//Create GraphQL request body
JObject := JObject.JObject;
JProperty := JProperty.JProperty('query', Query);
JObject.Add(JProperty);

//Add variables if needed
IF Variables <> '' THEN BEGIN
  JProperty := JProperty.JProperty('variables', Variables);
  JObject.Add(JProperty);
END;

//Add body to request object
RestRequest.AddParameter('application/json', JObject.ToString(), ParameterType.RequestBody);

//Execute request (RestSharp 106.3.1), must be wrapped in DotNet component
// IRestResponse :=  RestClient.Execute(RestClient, RestRequest);

//UPDATE - see below
IRestResponse := CustomRestWrapper.ExecuteRestCall(RestClient, RestRequest);

RestSharp.Execute() custom dll

Unfortunately, C/AL is unable to identify the “Execute()” method’s overloads, which results in the known “The function call was ambiguous” error. To solve this, you could wrap it in a small custom .NET dll, which consists of the following couple of lines:

using RestSharp;

namespace Comsol
{
    public static class ApiHelper
    {
        public static IRestResponse ExecuteRestCall(ref RestClient restClient, ref RestRequest restRequest)
        {
            return restClient.Execute(restRequest);
        }
    }
}

You could then use it just like the native RestSharp method:

//Execute REST request
IRestResponse := CustomRestWrapper.ExecuteRestCall(RestClient, RestRequest);

Parameters

NameTypeSubtype
QueryText
VariablesText
AdditionalHeadersDotNetSystem.Collections.Generic.Dictionary`2
IRestResponseDotNet RestSharp.IRestResponse

Local Variables

NameTypeSubtype
CustomRestWrapperDotNet Comsol.ApiHelper
RestClientDotNet RestSharp.RestClient
RestRequestDotNet RestSharp.RestRequest
RestMethodDotNet RestSharp.Method
ParameterTypeDotNet RestSharp.ParameterType
JObjectDotNet Newtonsoft.Json.Linq.JObject
JPropertyDotNetNewtonsoft.Json.Linq.JProperty
AdditionalHeaderDotNetSystem.Collections.Generic.KeyValuePair`2

Helper functions

Initialize RESTSharp

This method wraps some initialization of RestSharp.

LOCAL CreateRestClient(VAR RestClient : DotNet "RestSharp.RestClient";VAR RestRequest : DotNet "RestSharp.RestRequest";RestMethod : DotNet "RestSharp.Method")

//Instanciate RestSharp Client
RestClient := RestClient.RestClient(MySetup."Endpoint URL");
RestClient.Timeout := -1;

IF MySetup."Proxy Enabled" THEN BEGIN
  MySetup.TESTFIELD("Proxy Endpoint");
  MySetup.TESTFIELD("Proxy Port");

  WebProxy := WebProxy.WebProxy(MySetup."Proxy Endpoint", MySetup."Proxy Port");
  RestClient.Proxy := WebProxy;
END;

//Instanciate RestSharp Request
RestRequest := RestRequest.RestRequest(RestMethod);
NameTypeSubtype
RestClientDotNetRestSharp.RestClient
RestRequestDotNetRestSharp.RestRequest
RestMethodDotNetRestSharp.Method
WebProxyDotNet System.Net.WebProxy

Initialize Dictionary

To add e.g. additional headers in the different methods, we could initialize a global DotNet dictionary. In the ExecuteQuery_GraphQL() we will then loop through these heads to add them to the request. In my example, it’s just a local dictionary.

LOCAL CreateDictionary(VAR Dictionary : DotNet "System.Collections.Generic.Dictionary`2")

//Create DotNet dictionary
DotNetArray := DotNetArray.CreateInstance(GETDOTNETTYPE(DotNetType),2);
DotNetArray.SetValue(GETDOTNETTYPE(DotNetString),0);
DotNetArray.SetValue(GETDOTNETTYPE(DotNetString),1);

DotNetType := GETDOTNETTYPE(Dictionary);
DotNetType := DotNetType.MakeGenericType(DotNetArray);

Dictionary := DotNetActivator.CreateInstance(DotNetType);
NameTypeSubtype
DictionaryDotNetSystem.Collections.Generic.Dictionary`2
DotNetArrayDotNetSystem.Array
DotNetStringDotNetSystem.String
DotNetTypeDotNet System.Type
DotNetActivatorDotNet System.Activator

How-to GraphQL queries in Dynamics NAV

Using these methods you could actually just write down your GraphQL queries in Dynamics NAV as you would do in Postman.

//Write down your query
Query := 
      'query HeroNameAndFriends($herotype: Herotype) { ' +
            'hero(herotype: $herotype) { ' +
                'name ' +
                'friends { ' +
                    'name ' +
                '} ' +
            '} ' +
        '}';

//Define variables     
Variables := 
  STRSUBSTNO(
    '{ ' +
        '"input": { ' +
           '"herotype": "%1", ' +
        '} ' +
    '}',
    'JEDI'); 

//Execute the request
ExecuteQuery_GraphQL(Query, Variables, IRestResponse);

//Check for errors or parse response object
JsonResponse := JsonResponse.Parse(IRestResponse.Content);
NameTypeSubtype
QueryText
VariablesText
IRestResponseDotNet RestSharp.IRestResponse
JsonResponseDotNet Newtonsoft.Json.Linq.JObject

This is not a big deal but probably helps one or the other.

... is a technical consultant and developer at Comsol Unternehmenslösungen AG in Kronberg/Taunus. Major tasks are the architecture and implementation of complex, usually cross-system applications in and around Microsoft Dynamics 365 Business Central.

Leave a Reply

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