1. Kenneth Hughson
  2. PowerBuilder
  3. Tuesday, 26 April 2022 18:14 PM UTC

Does anyone have either

  1. any successful examples of using HTTPClient to consume a .Net Web API that requires XML to be passed to it? OR
  2. know why I’m receiving the error\issues below when trying to pass the XML?

I’m using PowerBuilder 2021 Professional Build 1288 to pass 7 parameters to the API, 6 are strings and 1 is XML. I have tried passing the XML to a string, XMLText and XMLDocument. Is there something I need to do to pass long XML? If the XML is submitted as string data Postman, ReadyAPI and the browser will return a 200 response with the data successfully processed.

Errors:

Bad Request - Invalid URL

HTTP Error 400. The request URL is invalid.

Or

An error has occurred.

A potentially dangerous Request.Path value was detected from the client (=\"...tResponse/<LOAD_PAYABLE>/AP/LO...\")

 

The Web API POST/GET method:

[HttpPost]

[Route("api/Payable/GetDocumentResponse/{txtxmldocument}/{doctype}/{docsubtype}/{docnumber}/{docpartysite}/{docusername}/{docpassword}")]

public string GetDocumentResponse(string txtxmldocument, string doctype, string docsubtype, string docnumber, string docpartysite, string docusername, string docpassword)

        {

               .........code

        }

 

The PowerBuilder code that has no effect on the Web API call has been removed, The ls_parameter variable will be using values passed into the method. The username and password are basically not being used.

Any XML will fail for example “<ADRESS></ADDRESS>. Actually any string value will also fail if it contains characters such as “<,>,?,/”

The PowerBuilder code:

httpClient lo_client

Integer li_ret , li_StatusCode

String ls_json, ls_body, ls_ret, ls_parameter

UO_DATASTORE_CONTROL lds_get_parm, lds_message

lo_client = Create HttpClient

lo_client.SetRequestHeader("Content-Type", "application/json;charset=UTF-8")

ls_parameter = '{"

li_ret = lo_client.SendRequest("POST", "https://localhost:44395/api/Payable/GetDocumentResponse/", ls_json, EncodingUTF8!)

if li_ret = 1 then

                li_StatusCode = lo_client.GetResponseStatusCode()

               if li_StatusCode = 200 then

                               lo_client.GetResponseBody(ls_body)

                               ls_ret = lo_client.GetResponseStatusText()

               else

                              ls_ret = lo_client.GetResponseStatusText()

               end if

end if

Return ls_ret

Destroy ( lo_client )

 Thanks, Ken H

 

 

 

Accepted Answer
Daryl Foster Accepted Answer Pending Moderation
  1. Thursday, 28 April 2022 06:27 AM UTC
  2. PowerBuilder
  3. # Permalink

Hi Kenneth,

I've create a sample API in SnapDevelop 2021 and a corresponding sample application in PB2019R3 to show how you can create an API that will accept a file and structured data. The API uses a Model (C# Class) that encapsulates the file and the structured data and is the argument to the API controller method. That is the easiest way of passing multiple arguments to an API controller.

The first method will accept a multipart form post which includes the file and the structured data, the second method will accept json which includes the file contents as a base64 encoded string.  Base64 encoding a file and embedding it in json is ok if it's relatively small, but I generally don't like to do it.  But these are two possible ways you can upload a file with structured data.

For the examples, I'm sending the xml as a string, but in real life you would probably read it from a file into a string.  I'm also only passing the doctype structured data from the Powerbuilder examples, but your other structured data (docsubtype, docnumber, docpartysite etc) are handled exactly the same as doctype, either as an additional form-data or json element.

C# Class to capture the data

    public class DocumentData
    {
        public IFormFile XmlFile { get; set; }
        public string XmlFileData { get; set; }
        public string doctype { get; set; }
        public string docsubtype { get; set; }
        public string docnumber { get; set; }
        public string docpartysite { get; set; }
        public string docusername { get; set; }
        public string docpassword { get; set; }
    }

 

C# Controller Method to receive a multipart form including a file

        [HttpPost]        
        public ActionResult GetDocumentResponseForm([FromForm] DocumentData documentData)
        {
            string xml = "";
            
            // This is one way of converting the xml file to a string, but you can do
            // whatever you need to do with it (save it to a file etc.).
            // Note. If the file is a binary file don't save it to a string. This is just an example!
            if (documentData.XmlFile != null && documentData.XmlFile.Length > 0)
            {
                using (var stream = new MemoryStream())
                {
                    documentData.XmlFile.CopyTo(stream);
                    stream.Position = 0;
                    StreamReader reader = new StreamReader(stream);
                    xml = reader.ReadToEnd();
                }
            }
            
            // For production do whatever you need to do with the file and data here
            // and send back a suitable response            
            
            // For testing, echo back the data received to show that it was received correctly
            return Ok($"{documentData.doctype}\r\n{xml}");
        }



C# Controller Method to receive a base64 encoded file in JSON

        [HttpPost]
        public ActionResult GetDocumentResponseJson([FromBody] DocumentData documentData)
        {
            string xml = "";
            
            // Convert the base64 encoded xml data to a string
            // Then you can do whatever you want with it (save to a file etc.).
            // Note. If the file is a binary file don't save it to a string. This is just an example!
            if (!String.IsNullOrEmpty(documentData.XmlFileData))
            {
                var xmlBytes = System.Convert.FromBase64String(documentData.XmlFileData);
                xml = Encoding.UTF8.GetString(xmlBytes);
            }
            
            // For production do whatever you need to do with the file and data here
            // and send back a suitable response            
            
            // For testing, echo back the data received to show that it was received correctly
            return Ok($"{documentData.doctype}\r\n{xml}");
        }

 

Powerbuilder Code to send xml as a multipart form to our api.  (This code is very similar to Marcin Jurkowski's answer below)

HttpClient lnv_HttpClient
integer li_rc, li_ResponseStatusCode
string ls_ResponseBody, ls_ResponseStatusText
string ls_uri

string ls_boundary
blob lblb_form_data

string ls_xml
string ls_doctype

// Set this to the server, port and path to your api
ls_uri = 'http://localhost:5000/api/document/GetDocumentResponseForm'

// I've just hard coded the xml here as a string, but in real life you would probably read this from a file
ls_xml = '<ADDRESS>Accidently Kelly Street</ADDRESS>'

// For brevity, I've only included doctype as the structured data to send, but you can add all the other structured data similiarly
ls_doctype = 'Test Document'

ls_boundary = '$$$Boundary$$$'

lblb_form_data =        blob('--' + ls_boundary + '~r~n', EncodingUTF8!)
lblb_form_data +=     blob('Content-Disposition: form-data; name="doctype"' + '~r~n~r~n' + ls_doctype + '~r~n', EncodingUTF8!)
lblb_form_data +=     blob('--' + ls_boundary + '~r~n', EncodingUTF8!)
lblb_form_data +=     blob('Content-Disposition: form-data; name="XmlFile"; filename="data.xml"~r~nContent-Type: application/xml~r~n~r~n', EncodingUTF8!)
lblb_form_data +=     blob(ls_xml, EncodingUTF8!)
lblb_form_data +=     blob('~r~n--' + ls_boundary + '--~r~n', EncodingUTF8!)

lnv_HttpClient = Create HttpClient

lnv_HttpClient.SetRequestHeader("Content-Type", "multipart/form-data; boundary=" + ls_boundary)
li_rc = lnv_HttpClient.SendRequest('POST', ls_uri, lblb_form_data)

// From here down is identical for both methods
if li_rc = 1 then
    // obtain the response data
    li_ResponseStatusCode = lnv_HttpClient.GetResponseStatusCode()
    ls_ResponseStatusText = lnv_HttpClient.GetResponseStatusText()
    lnv_HttpClient.GetResponseBody(ls_ResponseBody)

    if li_ResponseStatusCode = 200 then
        MessageBox('Debug - Response Received', ls_ResponseBody)
    else
        MessageBox('Debug - Error Calling API', string(li_ResponseStatusCode) + ' ' + ls_ResponseStatusText + '~r~n' + ls_ResponseBody)
    end if
else
    MessageBox('Debug - Error Sending API Request', 'Error calling API endpoint ' + ls_uri + ' - [' + string(li_rc) + ']')
end if

Destroy (lnv_HttpClient)


Finally some Powerbuilder code which will send xml as a base64 encoded element of a json object.

HttpClient lnv_HttpClient
integer li_rc, li_ResponseStatusCode
string ls_json, ls_ResponseBody, ls_ResponseStatusText
string ls_uri

string ls_xml
string ls_doctype

// Set this to the server, port and path to your api
ls_uri = 'http://localhost:5000/api/document/GetDocumentResponseJson'

// I've just hard coded the xml here as a string, but in real life you would probably read this from a file
ls_xml = '<ADDRESS>Accidently Kelly Street</ADDRESS>'
// For brevity, I've only included doctype as the structured data to send, but you can add all the other structured data similiarly
ls_doctype = 'Test Document'

// An example of how to base64 encode the xml so we can add it to the json
Blob lblb_Xml
string ls_xml_encoded
CoderObject lnv_CoderObject
lnv_CoderObject = Create CoderObject
lblb_Xml = Blob(ls_xml, EncodingANSI!)
ls_xml_encoded = lnv_CoderObject.Base64Encode(lblb_Xml)
Destroy lnv_CoderObject

// Dodgy way of creating the json. Don't do this in real life. You'd probably want to use JsonGenerator
ls_json = 	'{'
ls_json +=	'"XmlFileData" : "' + ls_xml_encoded + '",'
ls_json +=	'"doctype" : "' + ls_doctype + '"'
ls_json +=	'}'

//ls_json = '{"XmlFileData" : "PEFERFJFU1M+QWNjaWRlbnRseSBLZWxseSBTdHJlZXQ8L0FERFJFU1M+","doctype":"Test Document"}'

lnv_HttpClient = Create HttpClient

lnv_HttpClient.SetRequestHeader("Content-Type", "application/json;charset=UTF-8")
li_rc = lnv_HttpClient.SendRequest('POST', ls_uri, ls_json, EncodingUTF8!)

// From here down is identical for both methods
if li_rc = 1 then
	// obtain the response data
	li_ResponseStatusCode = lnv_HttpClient.GetResponseStatusCode()
	ls_ResponseStatusText = lnv_HttpClient.GetResponseStatusText()
	lnv_HttpClient.GetResponseBody(ls_ResponseBody)

	if li_ResponseStatusCode = 200 then
		MessageBox('Debug - Response Received', ls_ResponseBody)
	else
		MessageBox('Debug - Error Calling API', string(li_ResponseStatusCode) + ' ' + ls_ResponseStatusText + '~r~n' + ls_ResponseBody)
	end if
else
	MessageBox('Debug - Error Sending API Request', 'Error calling API endpoint ' + ls_uri + ' - [' + string(li_rc) + ']')
end if

Destroy (lnv_HttpClient)

 

Attachments (2)
Comment
There are no comments made yet.
Marcin Jurkowski Accepted Answer Pending Moderation
  1. Wednesday, 27 April 2022 09:26 AM UTC
  2. PowerBuilder
  3. # 1

This it the code I'm using to upload XML file to the API server for processing. Maybe you can re-use it for your project with some adjustments.

 

 

Attachments (1)
Comment
  1. Kenneth Hughson
  2. Wednesday, 27 April 2022 18:22 PM UTC
Thanks I appreciate the help, it looks like I may be able to use this; I will know in a day or two.
  1. Helpful
There are no comments made yet.
Andreas Mykonios Accepted Answer Pending Moderation
  1. Wednesday, 27 April 2022 05:53 AM UTC
  2. PowerBuilder
  3. # 2

As Daryl Foster mentioned you want to pass xml file, but content-type is set to "application/json;charset=UTF-8". If you want to pass xml, content-type should be set to "application/soap+xml" or "xml" or text "text/xml"... Actually the correct value for content-type should be provided by web service documentation..

Andreas.

Comment
There are no comments made yet.
Daryl Foster Accepted Answer Pending Moderation
  1. Wednesday, 27 April 2022 00:32 AM UTC
  2. PowerBuilder
  3. # 3

Hi Kenneth,

1. Can you share a screen shot or details of the Postman call which works. I find that if I have a working Postman example it is usually pretty straightforward to convert that to Powerbuilder.

2. Is the API one that you have written?  The route attribute seems to show that you pass all the arguments in the url path, including an xml document which seems pretty strange.  You would normally post any data in the body of a request, not part of the url path.

3. In your Powerbuilder code you are sending ls_json in your Http request, but it isn't set to a value. The ls_parameter variable isn't used at all in that code either. What data exactly are you trying to send to the api?

Comment
  1. Kenneth Hughson
  2. Wednesday, 27 April 2022 18:41 PM UTC
I will submit the Postman screen shots in another general message. In answer to your question I did write the Web API; however, I was not totally sure how to handle the PowerBuilder call. I started by tagging the parameters in the API as [FromBody] but found I could only use use one parameter. This was an attempt from what I could find for information and you are correct this is passing the arguments in the path which is somethng we do not want but it provided a start point to ask people with experience. The ls_json and ls_parameter was just a mistake in the shortening the code to send with the message. Thanks I appreciate the help.
  1. Helpful
  1. Daryl Foster
  2. Wednesday, 27 April 2022 21:07 PM UTC
Thanks Kenneth, I look forward to seeing the Postman screen shots. Are you open to changing the api a bit? I think the bigger problem is trying to send a document as part of the URL path. It could possibly work if the document is small but could be a problem with larger documents. In general it sounds like a bad idea.



It probably doesn’t work from PB because xml has characters that will mess up the path. It probably works from Postman because it will likely automatically URL Encode the XML.



Two options for your api is to send all the structured data as json and use [FromBody]. Or else send the data as a multipart form.
  1. Helpful
  1. Daryl Foster
  2. Thursday, 28 April 2022 06:28 AM UTC
Hi Kenneth, I've actually put together a sample api and PB application to show a couple of methods of passing a file and structured data to an api. See my response above. Good luck
  1. Helpful
There are no comments made yet.
Armeen Mazda @Appeon Accepted Answer Pending Moderation
  1. Tuesday, 26 April 2022 20:55 PM UTC
  2. PowerBuilder
  3. # 4
Comment
  1. Kenneth Hughson
  2. Friday, 29 April 2022 09:54 AM UTC
Daryl Foster and Marcin Jurkowski had the answer that worked for me, thanks for sharing the code. All of the answers were tried and helpful, in any case, great for learning. Thanks everyone for taking time to help, it was very much appreciated.
  1. Helpful
There are no comments made yet.
  • Page :
  • 1


There are no replies made for this question yet.
However, you are not allowed to reply to this question.