Using a x-www-form-urlencoded Rest API with Business central (to get token)

Brax007Brax007 Member Posts: 26
Hi everyone,

I'm trying to develop a function with AL that permits to Post a url encoded content.
ce863zuqnnfr.png

It works fine with postman. The following procedure that I've started developping doesn't work :
procedure GetToken() ResponseText: text;

    Var
        client: HttpClient;
        cont: HttpContent;
        header: HttpHeaders;
        response: HttpResponseMessage;
        Jobject: JsonObject;
        tmpString: Text;
        TypeHelper: Codeunit "Type Helper";
        granttype: text;
        clienid: text;
        username: text;
        password: text;
    Begin
        granttype := 'xxxxx';
        clienid := 'xxxxxx';
        username := 'xxxxxxxxx';
        password := 'xxxxxxxxxxxxx';
        Jobject.Add('grant_type', TypeHelper.UrlEncode(granttype));
        Jobject.Add('client_id', TypeHelper.UrlEncode(clienid));
        Jobject.Add('username', TypeHelper.UrlEncode(username));
        Jobject.Add('password', TypeHelper.UrlEncode(password));
        Jobject.WriteTo(tmpString);
        cont.WriteFrom(tmpString);
        cont.ReadAs(tmpString);

        cont.GetHeaders(header);
        header.Add('charset', 'UTF-8');
        header.Remove('Content-Type');
        header.Add('Content-Type', 'application/x-www-form-urlencoded');

        client.Post('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', cont, response);
        response.Content.ReadAs(ResponseText);

    end;

Can someone help me please?
I get this response everytime :

{
"error": "invalid_request",
"error_description": "Missing form parameter: grant_type"
}

Ps : it doesn't work even without encoding the parameters.

Thanks in advance

Best Answer

  • ftorneroftornero Member Posts: 459
    Answer ✓
    Hello @Brax007,

    With your code you are sending a JSON format and you need to send something like this:
    grant_type=grant_type_value&client_id=client_id_value&username=username_value&password=password_value
    

    And every value must be urlEncode like you already do.

    Regards.

Answers

  • ftorneroftornero Member Posts: 459
    Answer ✓
    Hello @Brax007,

    With your code you are sending a JSON format and you need to send something like this:
    grant_type=grant_type_value&client_id=client_id_value&username=username_value&password=password_value
    

    And every value must be urlEncode like you already do.

    Regards.
  • irasoelbaksirasoelbaks Netherlands, theMember Posts: 119
    edited 2021-02-11
    What could also help is to inspect the full request (body + header) from BC and the full request from Postman with a webdebugging tool.

    And in addition to the reply of @ftornero your BC code is similar to a 'raw' body while you should use 'x-www-form-urlencoded'.

    Code I used in C/AL:
    - Put the key/value pairs in an Outstream
    - TempBlob.Blob.CREATEINSTREAM(InStream);
    - TempBlob.Blob.CREATEOUTSTREAM(OutStr);
    - Copy to Instream
    - SetContentType to application/x-www-form-urlencoded (HttpWebRequestMgt.SetContentType('application/x-www-form-urlencoded');)
    - Add the stream to the body of your message: HttpWebRequestMgt.AddBodyBlob(TempBlob);
  • Brax007Brax007 Member Posts: 26
    @ftornero That was it. Thank you!
  • julkifli33julkifli33 Member Posts: 1,039
    edited 2021-08-16
    Hi all,
    how do we add this headers?
    for GET
    l733tcdcvpb6.png
  • ftorneroftornero Member Posts: 459
    Hello @julkifli33, the same way as the code above in this post:

    dh4ykt13r1jy.png

    Change the "application/x-www-form-urlencoded" for "application/json" and add the two others one.

    Regards.
  • julkifli33julkifli33 Member Posts: 1,039
    edited 2021-08-20
    Hi @ftornero
    this is my code
    gcontent.GetHeaders(lheaders);
            lheaders.Remove('Content-Type');
            lheaders.Add('Content-Type', 'application/Json');
            lheaders.Add('X-IBM-Client-Id', ClientID);
            lheaders.Add('X-IBM-Client-Secret', ClientSecret);
    
            if not gHttpClient.get(RequestUrl, gResponseMsg) then
                Error('API Authorization token request failed...');
    
            gResponseMsg.Content().ReadAs(responseText);
            Message('%1', responseText);
    

    but this is my return value
    ihqjnstxwt6x.png

    it supposed to be like this
    in postman i tested is ok
    {
        "returnCode": "10",
        "data": {
            "url": "https://*********"
        },
        "info": {
            "fieldInfoList": []
        }
    }
    
  • ftorneroftornero Member Posts: 459
    Hello @julkifli33 ,

    With this code

    dy2qc8ojb0vf.png

    You don't pass the RequestMsg that you need to pass.

    Use

    kqf9m7tbfzr1.png

    This is the URL for the documentation

    https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/methods-auto/httpclient/httpclient-data-type

    Regards
  • julkifli33julkifli33 Member Posts: 1,039
    Hi @ftornero ,
    I tried this before
     // Retrieve the contentHeaders associated with the content
            content.GetHeaders(contentHeaders);
            contentHeaders.Clear();
            contentHeaders.Add('X-IBM-Client-Id', ClientID);
            contentHeaders.Add('X-IBM-Client-Secret',ClientSecret);
            contentHeaders.Add('Content-Type', 'application/json');
            contentHeaders.Add('Accept', 'application/json');
            request.Content := content;
            request.SetRequestUri(uri);
            request.Method := 'GET';
            client.Send(request, response);
    
            // Read the response content as json.
            response.Content().ReadAs(responseText);
            Message('%1', responseText);
    

    got this error message
    zrc3ui5dxu7x.png
  • ftorneroftornero Member Posts: 459
    Hello @julkifli33, the headers in AL are a little complicated, this is the way I do in an scenario like your:
        var
            baseUrl: Text;
            Client: HttpClient;
            Content: HttpContent;
            RequestMsg: HttpRequestMessage;
            RequestHeader: HttpHeaders;
            ContentHeader: HttpHeaders;
            ResponseMSg: HttpResponseMessage;
            authorizationValue: Text;
    
        begin
            baseUrl := GetBaseURL();
    
            Content.Clear();
    
            // Write content
            if StrLen(contentText) > 0 then
                Content.WriteFrom(contentText);
    
            if method <> 'GET' then begin
                ContentHeader.Clear();
                Content.GetHeaders(ContentHeader);
                ContentHeader.Remove('Content-Type');
                if contentType <> '' then
                    ContentHeader.Add('Content-Type', contentType)
                else
                    ContentHeader.Add('Content-Type', 'application/json');
                RequestMsg.Content(Content);
            end;
    
            RequestHeader.Clear();
            RequestMsg.GetHeaders(RequestHeader);
            authorizationValue := CreateAuthorization(apiKey);
            RequestHeader.Add('Authorization', authorizationValue);
    
            RequestMsg.SetRequestUri(baseUrl + apiUrl);
            RequestMsg.Method(method);
    
            if Client.Send(RequestMsg, ResponseMSg) then begin
    ......
    

    There is two header, so put the "Content-Type" in the first one and the autorization part in the second one, but if the method is GET I think that the "Content-Type" part is no necesary because you don't send any data in this case.

    Regards
  • julkifli33julkifli33 Member Posts: 1,039
    Hi @ftornero
    I have sent pm to you about my key.
    there's another error message
    bhdijllir3mq.png
  • ftorneroftornero Member Posts: 459
    Hello @julkifli33,

    I already sent my answer.

    Regards
  • julkifli33julkifli33 Member Posts: 1,039
    Hi @ftornero ..
    i am still stuck with this.
    any idea or link you can share how to use azure function, and then BC call this azure function?
  • ftorneroftornero Member Posts: 459
    Hello @julkifi33

    Try this code
    pageextension 50100 CustomerListExt extends "Customer List"
    {
        actions
        {
            addafter("&Customer")
            {
                action(Singapur)
                {
                    ApplicationArea = All;
                    Caption = 'Singapur';
                    Promoted = true;
                    PromotedIsBig = true;
                    PromotedOnly = true;
                    Image = SendTo;
    
                    trigger OnAction()
                    var
                        CodError: Text;
                        ResTxt: Text;
                    begin
                        CallGETRESTApi(CodError, ResTxt);
                        Message(ResTxt);
                    end;
                }
            }
        }
    
        procedure CallGETRESTApi(var CodigoError: Text; var ResTxt: Text): Boolean
        var
            baseUrl: Text;
            Client: HttpClient;
            RequestMsg: HttpRequestMessage;
            ResponseMSg: HttpResponseMessage;
    
        begin
            baseUrl := 'https://ftomps64.azurewebsites.net/api/Singapur?code=GFb/v6xv9auJlEx7OOrqGhnuHYBhyoOmZV0SeGU2wezbtVsTBSQNAQ==';
    
            RequestMsg.SetRequestUri(baseUrl);
            RequestMsg.Method('GET');
    
            if Client.Send(RequestMsg, ResponseMSg) then begin
                if ResponseMSg.IsSuccessStatusCode() then begin
                    ResponseMSg.Content.ReadAs(ResTxt);
                    CodigoError := '';
                    exit(true);
                end else begin
                    CodigoError := Format(ResponseMSg.HttpStatusCode()) + ' ' + ResponseMSg.ReasonPhrase();
                    ResponseMSg.Content.ReadAs(ResTxt);
                    if ResTxt = '' then
                        ResTxt := CodigoError;
                    exit(false);
                end;
            end else begin
                CodigoError := 'ERROR';
                if ResponseMSg.Content.ReadAs(ResTxt) then;
                if ResTxt = '' then
                    ResTxt := CodigoError;
                exit(false);
            end;
        end;
    }
    

    And you must get this message:

    ys6osdickfuo.png

    This is the Azure Function Code, the Client-ID and the Client-Secret must been the correct ones.
    using namespace System.Net
    
    # Input bindings are passed in via param block.
    param($Request, $TriggerMetadata)
    
    #--------------------------------
    function Execute-GetRequest 
    #--------------------------------
    ( 
      [String] $URL
    ) 
    { 
      $WebRequest = [System.Net.WebRequest]::Create($URL) 
      $WebRequest.Headers.Add("X-IBM-Client-ID",  "xxxxx-xxxxx-xxxx-xxxxx-xxxxxx")
      $WebRequest.Headers.Add("X-IBM-Client-Secret", "SssssSSSSSsssSSSSssss")
      $WebRequest.ContentType = "application/json"
      $WebRequest.Method      = "GET"         
    
      try {
        $resp = $WebRequest.GetResponse() 
        $responseStream = $resp.GetResponseStream() 
        $Reader = [System.IO.StreamReader]($responseStream) 
        $ReturnXml = $Reader.ReadToEnd() 
        $responseStream.Close() 
        return $ReturnXml 
      } catch {
        Write-Error $Error[0]
      }
    }
    
    $url = "https://apisandbox.iras.gov.sg/iras/sb/Authentication/CorpPassAuth?callback_url=https://businesscentral.dynamics.com/sandbox&tax_agent=false&state=StanleyState&scope=GSTF7SubCP"
    
    $ret = Execute-GetRequest $url
    
    # Associate values to output bindings by calling 'Push-OutputBinding'.
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::OK
        Body = $ret
    })
    

    You could elaborate the Azure function to get as parameters the Method, the URL, the X-IBM-Client-ID, etc.

    This Azure function will be active for a couple of days.

    Regards.
  • julkifli33julkifli33 Member Posts: 1,039
    Hi @ftornero ,
    thanks for the effort
    I am not really familiar with this azure function.
    are we able to pass parameter to this azure function?
    such as clientid, clientsecret and url
  • ftorneroftornero Member Posts: 459
    Hello @julkifli33,

    You must to create the Azure function, and sure you can pass parameters to it.

    The AL code to do the new call
    
    pageextension 50100 CustomerListExt extends "Customer List"
    {
        actions
        {
            addafter("&Customer")
            {
                action(Singapur)
                {
                    ApplicationArea = All;
                    Caption = 'Singapur';
                    Promoted = true;
                    PromotedIsBig = true;
                    PromotedOnly = true;
                    Image = SendTo;
    
                    trigger OnAction()
                    var
                        CodError: Text;
                        ResTxt: Text;
                    begin
                        CallGETRESTApi(CodError, ResTxt);
                        Message(ResTxt);
                    end;
                }
            }
        }
    
        procedure CallGETRESTApi(var CodigoError: Text; var ResTxt: Text): Boolean
        var
            baseUrl: Text;
            Client: HttpClient;
            RequestMsg: HttpRequestMessage;
            ResponseMSg: HttpResponseMessage;
            Body: JsonObject;
            Content: HttpContent;
            ReqTxt: Text;
            ContentHeader: HttpHeaders;
            RequestHeader: HttpHeaders;
    
        begin
            baseUrl := 'https://ftomps64.azurewebsites.net/api/Singapur?code=GFb/v6xv9auJlEx7OOrqGhnuHYBhyoOmZV0SeGU2wezbtVsTBSQNAQ==';
    
            RequestHeader.Clear();
            RequestMsg.GetHeaders(RequestHeader);
            RequestHeader.Add('Accept', 'application/json');
    
            RequestMsg.SetRequestUri(baseUrl);
            RequestMsg.Method('POST');
    
            Body.Add('url', 'https://apisandbox.iras.gov.sg/iras/sb/Authentication/CorpPassAuth?callback_url=https://businesscentral.dynamics.com/sandbox&tax_agent=false&state=StanleyState&scope=GSTF7SubCP');
            Body.Add('method', 'GET');
            Body.Add('clientID', 'xxxx-xxxxx-xxxx-xxxx-xxxxxxxxx');
            Body.Add('clientSecret', 'Ssssssssssssssssssssssssssssss');
    
            // Write content
            Body.WriteTo(ReqTxt);
            Content.WriteFrom(ReqTxt);
    
            ContentHeader.Clear();
            Content.GetHeaders(ContentHeader);
            ContentHeader.Remove('Content-Type');
            ContentHeader.Add('Content-Type', 'application/json');
            RequestMsg.Content(Content);
    
            if Client.Send(RequestMsg, ResponseMSg) then begin
                if ResponseMSg.IsSuccessStatusCode() then begin
                    ResponseMSg.Content.ReadAs(ResTxt);
                    CodigoError := '';
                    exit(true);
                end else begin
                    CodigoError := Format(ResponseMSg.HttpStatusCode()) + ' ' + ResponseMSg.ReasonPhrase();
                    ResponseMSg.Content.ReadAs(ResTxt);
                    if ResTxt = '' then
                        ResTxt := CodigoError;
                    exit(false);
                end;
            end else begin
                CodigoError := 'ERROR';
                if ResponseMSg.Content.ReadAs(ResTxt) then;
                if ResTxt = '' then
                    ResTxt := CodigoError;
                exit(false);
            end;
        end;
    }
    

    And the new Azure function
    using namespace System.Net
    
    # Input bindings are passed in via param block.
    param($Request, $TriggerMetadata)
    
    $method = $Request.Body.method
    $url = $Request.Body.url
    $clientID = $Request.Body.clientID
    $clientSecret = $Request.Body.clientSecret
    
    #--------------------------------
    function Execute-GetRequest 
    #--------------------------------
    ( 
      [String] $pURL,
      [String] $pMethod,  
      [String] $pClientID,
      [String] $pClientSecret
    ) 
    { 
      $WebRequest = [System.Net.WebRequest]::Create($pURL) 
      $WebRequest.Headers.Add("X-IBM-Client-ID",  $pClientID)
      $WebRequest.Headers.Add("X-IBM-Client-Secret", $pClientSecret)
      $WebRequest.ContentType = "application/json"
      $WebRequest.Method      = $Method         
    
      try {
        $resp = $WebRequest.GetResponse() 
        $responseStream = $resp.GetResponseStream() 
        $Reader = [System.IO.StreamReader]($responseStream) 
        $ReturnXml = $Reader.ReadToEnd() 
        $responseStream.Close() 
        return $ReturnXml 
      } catch {
        Write-Error $Error[0]
      }
    }
    
    $ret = Execute-GetRequest $url $method $clientID $clientSecret
    
    # Associate values to output bindings by calling 'Push-OutputBinding'.
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::OK
        Body = $ret
    })
    
    

    Regards
  • ftorneroftornero Member Posts: 459
    Sorry

    This line:
      $WebRequest.Method      = $Method  
    

    Must be this one:
      $WebRequest.Method      = $pMethod  
    
  • GaboPrinz00GaboPrinz00 Member Posts: 1
    edited 2022-01-25


    ``ñ
Sign In or Register to comment.