Using NameValueCollection and webClient

Posted by goo on 17-Aug-2018 05:49

11.7

Why do I get an array error doing this?

  wcURI = NEW System.Uri(cTokenEndpoint).
  wc = NEW System.Net.WebClient().
  oParameters = New System.Collections.Specialized.NameValueCollection().
  oParameters:ADD("client_id",'oidc_ovf_conf').
  oParameters:ADD("grant_type",'authorization_code').
/*  oParameters:ADD("code",cCode).                                   */
/*  oParameters:ADD("redirect_uri",URL-ENCODE(cRedirectURL,'query')).*/
 
  PUT-STRING(rawAuth,1) = cClientid + ':' + cClientSecret.
  lcBASE64Auth = BASE64-ENCODE(rawAuth).
  wc:Encoding = System.Text.Encoding:UTF8.
  wc:Headers:Add("Content-Type", "application/x-www-form-urlencoded").
  wc:Headers:Add("Authorization", "Basic " + lcBase64Auth).
 
 // lcResponse = wc:UploadString(wcURI,'POST',cParamList).
  lcResponse = wc:UploadValues(wcURI,'POST',oParameters).    <------------------------------ this gives me an error saying that it did not expect an array….
Syntax check:
An array was specified in an Expression, on the right-hand side of an assignment, or as a parameter when no array is appropriate or expected (361)
It seems to be correct syntax regarding Microsoft.... what am I doing wrong?
UploadValues(Uri, String, NameValueCollection)

Uploads the specified name/value collection to the resource identified by the specified URI, using the specified Method.

Posted by tbergman on 17-Aug-2018 07:29

The UploadValues method of the web client returns a Byte array.

def var foo as "System.Byte[]".

foo = <your statement>.

Now you need to do something with the byte array. You could either pass it to another .net method if appropriate or convert it to something usable in the Progress world. The method below will convert a byte array to a memptr. From there you can do what's needed.

METHOD PUBLIC STATIC MEMPTR ByteArrayToMemptr( nBytes AS "System.Byte[]" ):

   DEFINE VARIABLE nPtr       AS System.IntPtr NO-UNDO.

   DEFINE VARIABLE mPtr       AS MEMPTR        NO-UNDO.

   DEFINE VARIABLE PointerLoc AS INT64         NO-UNDO.

   set-size(mPtr) = nBytes:LENGTH.

   /* In 11.7, due to the new compile used, Progress is much more likely to choose

      a memory pointer that exceeds the 32 bit limit of an integer.

      Using this intermediate INT64 solves the problem */

   PointerLoc = GET-POINTER-VALUE(mPtr).

   nPtr = NEW System.IntPtr(PointerLoc).

   System.Runtime.InteropServices.Marshal:Copy(nBytes, 0, nPtr, nBytes:LENGTH).

   RETURN mPtr.

   FINALLY:

     nPtr = ?.

     nBytes = ?.

     set-size(mPtr) = 0.

   END.

 END METHOD.

All Replies

Posted by bronco on 17-Aug-2018 06:18

Array on the right-hand side is the Byte[] array returned by UploadValues. You're assigning it to what appears to be a longchar. That does not compute...

Posted by goo on 17-Aug-2018 06:55

aaah, what would be the correct approach ? Can't do a

def var lcResponse as longchar extent no-undo.

Posted by tbergman on 17-Aug-2018 07:29

The UploadValues method of the web client returns a Byte array.

def var foo as "System.Byte[]".

foo = <your statement>.

Now you need to do something with the byte array. You could either pass it to another .net method if appropriate or convert it to something usable in the Progress world. The method below will convert a byte array to a memptr. From there you can do what's needed.

METHOD PUBLIC STATIC MEMPTR ByteArrayToMemptr( nBytes AS "System.Byte[]" ):

   DEFINE VARIABLE nPtr       AS System.IntPtr NO-UNDO.

   DEFINE VARIABLE mPtr       AS MEMPTR        NO-UNDO.

   DEFINE VARIABLE PointerLoc AS INT64         NO-UNDO.

   set-size(mPtr) = nBytes:LENGTH.

   /* In 11.7, due to the new compile used, Progress is much more likely to choose

      a memory pointer that exceeds the 32 bit limit of an integer.

      Using this intermediate INT64 solves the problem */

   PointerLoc = GET-POINTER-VALUE(mPtr).

   nPtr = NEW System.IntPtr(PointerLoc).

   System.Runtime.InteropServices.Marshal:Copy(nBytes, 0, nPtr, nBytes:LENGTH).

   RETURN mPtr.

   FINALLY:

     nPtr = ?.

     nBytes = ?.

     set-size(mPtr) = 0.

   END.

 END METHOD.

Posted by bronco on 17-Aug-2018 07:52

The correct approach would be to read the manuals so that you actually know what you are doing ;-)

An even better approach would be to stop using .NET and use the 4GL instead, although with SSL/TLS that can be tricky I have to admit.

Byte[] suggests memptr...

Posted by goo on 17-Aug-2018 08:13

I am taking the Learning by trying, but I should probably do learning by Reading [:)].

I forgot to check the Return value… It went from string to byte array… my bad ! [:)]

I was uploading and was not thinging that the returnvalue was changed.

Sorry for that !

Posted by Peter Judge on 17-Aug-2018 13:08

I've created an ABL equivalent using the HTTP client. It's at https://github.com/PeterJudge-PSC/http_samples/blob/master/http_client/post_form_name_value.p 

The meat of it is below (minus variable defs and usings)

assign hc    = ClientBuilder:Build():Client
       // the first param is a realm you can leave blank
       creds = new Credentials('':u, clientId, clientSecret)
       .
// build the request
req = RequestBuilder:Post(tokenEndpoint,
                          // prior to 11.7.3 you had to pass in a valid object here. in 11.7.3+ you can not
                          new StringStringMap() )
        :AddFormData('client_id', 'oidc_ovf_conf')
        :AddFormData('grant_type','authorization_code')
        :UsingBasicAuthentication(creds)
        :Request.

// make the request
resp = hc:Execute(req).

// process the response
message 
resp:StatusCode         skip   // 200 is all went well
resp:ContentType        skip   // something like application/json or application/x-www-form-urlencoded
resp:ContentLength      skip   // number of bytes, if you care
view-as alert-box.

// this Entity property is DEFINED as Progress.Lang.Object
// but the actual class/type will be something else; typically something that matches the ContentType 
// now get the response data in  a nice, strongly-typed Object form
// We can either decide what to do based on the ContentType or on the object's type

// approach 1
case resp:ContentType:
    when 'application/json':u then
        assign jsonData = cast(resp:Entity, JsonObject).
    // other types    
end case.

This thread is closed