Sending a POST command - x++

I've the below code that is working correctly at c# how can I write the equivalent code using x++?
using System.Net.Http.Headers;
var client = new HttpClient();
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri("http://localhost:8080/users"),
Content = new StringContent("{\"id\":\"2\",\"name\":\"Karam\"}")
{
Headers =
{
ContentType = new MediaTypeHeaderValue("application/json")
}
}
};
using (var response = await client.SendAsync(request))
{
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}

Since you can use .NET in x++, it is pretty similar. However, you have to give up some syntactic sugar and deal with some oddities on how x++ deals with .NET types and objects. This is why usually, such code is not written directly in x++, but in a C# client that is in turned called from x++.
In case you really want to do this in x++, here is some code for Dynamics 365 Finance and Operations where I tried to recreate the question's code as faithfully as possible in x++. I added some comments to explain some of the differences. If earlier versions (e.g. Dynamics AX 2012) are used, you may need to add some InteropPermission.
Additional information can be found at X++ and C# Comparison and .NET Interop from X++.
using System.Net.Http;
using System.Net.Http.Headers;
internal final class SOSendingAPOSTCommand
{
public static void main(Args _args)
{
// client is a x++ keyword
var httpClient = new HttpClient();
// x++ does not support object initializers, so attributes of new objects need to be set separately
var request = new HttpRequestMessage();
// static methods and fields are called with double colon in x++
request.Method = HttpMethod::Post;
// if using System; is used, the exception type of the info method is not decidable anymore
request.RequestUri = new System.Uri("https://httpbin.org/post");
var stringContent = new StringContent("{\"id\":\"2\",\"name\":\"Karam\"}");
// x++ does not like multiple chained calls with .NET objects, so stringContent.Headers.ContentType cannot be used
var headers = stringContent.Headers;
headers.ContentType = new MediaTypeHeaderValue("application/json");
request.Content = stringContent;
// there is no await or async in x++
var response = httpClient.SendAsync(request).Result;
response.EnsureSuccessStatusCode();
var body = response.Content.ReadAsStringAsync().Result;
info(body);
}
}
When I execute this runnable class on my local VHD environment on version 10.0.29, I get the following text as a result. Note that I'm using https://httpbin.org/post as service to test the POST request because I don't have access to the local http://localhost:8080/users of the question.
{
"args": {},
"data": "{\"id\":\"2\",\"name\":\"Karam\"}",
"files": {},
"form": {},
"headers": {
"Content-Length": "25",
"Content-Type": "application/json",
"Host": "httpbin.org",
"X-Amzn-Trace-Id": "Root=1-63d51491-66f3cd9964eda82b5e496448"
},
"json": {
"id": "2",
"name": "Karam"
},
"origin": "212.117.82.182",
"url": "https://httpbin.org/post"
}

Related

Mapping a custom JSON into ClaimActions

I am developing a Angular based website in which a user is required to login using a custom OAuth2 third party authentication provider. .Net core web API is the backend. The response received from the user end point is in JSON and it is having the following format:
{
"dataSources": {
"profile": {
"username": "xyz"
}
},
"profile": {
"id": "87dfkajdfd998df"
},
"errors": {}
}
The code I am currently using is as follows:
builder.AddOAuth(oauth2Configuration.Issuer,
options => {
options.ClientId = oauth2Configuration.ClientId;
options.ClientSecret = oauth2Configuration.ClientSecret;
options.Scope.Add(oauth2Configuration.Scope);
options.ClaimsIssuer = oauth2Configuration.Issuer;
options.CallbackPath = new PathString(oauth2Configuration.ResponseType);
options.AuthorizationEndpoint = oauth2Configuration.Authority;
options.TokenEndpoint = oauth2Configuration.EndSessionEndpoint;
options.UserInformationEndpoint = oauth2Configuration.UserInfoEndpoint;
options.SaveTokens = true;
// Below mapping does not seem to work
options.ClaimActions.MapJsonSubKey(ClaimTypes.Name, "dataSources", "profile.username");
options.ClaimActions.MapJsonKey(ClaimTypes.SerialNumber, "profile.id");
// Remaining code
})
After authenticating with the above code, the claims list is always empty.
Has anyone encountered a similar situation in which claim mapping was done for custom JSON data?
That seems the OAuth authentication handler itself won't help call the endpoint , you need to manually make a call to obtain use's profile from UserInfo endpoint in OnCreatingTicket event :
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user);
}
And make claim mapping manually based on your scenario - parse/read the json using JSON.NET and add to user's princple . Please refer to below articles for code samples :
https://www.jerriepelser.com/blog/authenticate-oauth-aspnet-core-2/
https://stackoverflow.com/a/46064936/5751404

nSwag change responseType: "blob"

I have a problem. I am using asp.net core 3 web api. The Angular 8 app client is generated with nSwag version 13.2.1.0. The specificatio is generated Swashbuckle.AspNetCore 5.
The result I get is:
**
* #param body (optional)
* #return Success
*/
seller(body: RegisterSellerRequest | undefined): Observable<TokenResponse> {
let url_ = this.baseUrl + "/api/register/seller";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(body);
let options_: any = {
body: content_,
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Content-Type": "application/json-patch+json",
"Accept": "application/json"
})
};
As you can see the responseType: "blob" is generated, and that's not good for our angular app's interceptor.
Is there a way to set response to be application/json?!
In my controllers I set the swagger attributes like this:
[ApiExplorerSettings(GroupName = Constatns.PublicSwaggerGroup)]
[SwaggerOperation(OperationId = "registerSeller")]
[HttpPost("api/register/seller")]
[ValidateModel]
[AllowAnonymous]
[ProducesResponseType((int)HttpResponseType.OK, Type = typeof(TokenResponse))]
[ProducesResponseType((int)HttpResponseType.BadRequest)]
[Produces("application/json")]
public async Task<TokenResponse> RegisterSeller([FromBody] RegisterSellerRequest data)
{}
I think its currently no simple way to change that. Its the simplest way to load everything as blob and then transform it to json or binary depending the response type. Changing that would mean that the generator templates get much more complicated.

The generated JSON Web Token is not accepted by Google API Service

I have created a service account and downloaded my JSON Credential on Google Cloud Platform. I need to make REST POST call in .NET to DialogFlow Service API. At this moment, I can do it only with a generated token in PowerShell. Since, I need to do it all from script, I need to generate a JWT to pass as my bearer in my REST call. My Problem is that the generated JWT is not honored by Google.
I get my response in PowerShell based on this doc page and I replicate sample codes from this doc page to create my JWT.
public static string GetSignedJwt(string emailClient, string
dialogueFlowServiceApi, string privateKeyId, string privateKey, string
jsonPath)
{
// to get unix time in seconds
var unixTimeSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
// start time of Unix system
var origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
// adding milliseconds to reach the current time, it will be used for issueAt time
var nowDataTime = origin.AddSeconds(unixTimeSeconds);
// one hour after the current time, it will be used for expiration time
var oneHourFromNow = nowDataTime.AddSeconds(3600);
// holder of signed json web token that we will return at the end
var signedJwt = "";
try
{
// create our payload for Jwt
var payload = new Dictionary<string, object>
{
{"iss", emailClient},
{"sub", emailClient},
{"aud", dialogueFlowServiceApi},
{"iat", nowDataTime},
{"exp", oneHourFromNow}
};
// create our additional headers
var extraHeaders = new Dictionary<string, object>
{
{"kid", privateKeyId}
};
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
signedJwt = encoder.Encode(extraHeaders, payload, privateKey);
}
catch (Exception e)
{
Console.WriteLine(e);
// return null if there has been any error
return null;
}
finally
{
Console.WriteLine(signedJwt);
}
return signedJwt;
}
Notice that, it is needed to be signed in RSA256 by passing public and private keys, as Google did it in Java sample snippet, however, my equivalent in .Net gives me only Object reference not set to an instance of an object when I use that algorithm:
var key = RSA.Create(privateKey);
IJwtAlgorithm algorithm = new RS256Algorithm(null, key);
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
signedJwt = encoder.Encode(extraHeaders, payload, privateKey);
Besides of correct keys, I am using https://dialogflow.googleapis.com/google.cloud.dialogflow.v2beta1.Intents as dialogFlow service API key.
I expect it that my generated JWT gets accepted, however it is rejected by Google.
1) You are using the wrong algorithm
Change this line of code:
IJwtAlgorithm algorithm = new RS256Algorithm(null, key);
To this:
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
2) For the JWT headers:
var additional_headers = new Dictionary<string, object>
{
{ "kid", privateKeyId },
{ "alg", "RS256" },
{ "typ", "JWT" }
};
3) Your JWT Payload does not include a scope. I am not sure which scope you need but here is an example. Add this to the payload before creating the JWT:
string scope = "https://www.googleapis.com/auth/cloud-platform";
var payload = new Dictionary<string, object>
{
{"scope", scope},
{"iss", emailClient},
{"sub", emailClient},
{"aud", dialogueFlowServiceApi},
{"iat", nowDataTime},
{"exp", oneHourFromNow}
};
4) For most Google APIs (not all) you also need to exchange the Signed JWT for a Google OAuth Access Token:
public static string AuthorizeToken(string token, string auth_url)
{
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var content = new NameValueCollection();
// Request a "Bearer" access token
content["assertion"] = token;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
var response = client.UploadValues(auth_url, "POST", content);
return Encoding.UTF8.GetString(response);
}
The Authorization URL for above:
string auth_url = "https://www.googleapis.com/oauth2/v4/token";

"Response status code does not indicate success: 500 (Internal Server Error)" while creating Test Suite through TFS Rest API

While trying to create a Test Suite using TFS 2017 REST API, I am getting the error:
System.Net.Http.HttpRequestException - Response status code does not
indicate success: 500 (Internal Server Error)
Code I tried:
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string base64StringPat = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", Configs.Pat)));
AuthenticationHeaderValue authHeader = new AuthenticationHeaderValue("Basic", base64StringPat);
client.DefaultRequestHeaders.Authorization = authHeader;
string url = "http://vmctp-tl-mtm:8080/tfs/DefaultCollection/SgkProject/_apis/test/Plans/7/Suites/8?api-version=1.0";
var content = new StringContent("{\"suiteType\":\"StaticTestSuite\",\"name\":\"Module1\"}", Encoding.UTF8, "application/json");
using (HttpResponseMessage response = client.PostAsync(url, content).Result)
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
I have used this documentation from Microsoft to call the API: Create a test suite
Please guide me in fixing the issue.
HTTP code 500 means that this is an error on your server. The server threw an exception when trying to process this POST request.
So, this error has nothing to do with HttpClient. Just check your server first and see what causes the exception.
A possibility is that the specified content type is not expected by the server. POST a StringContent will set the content type to text/plain. You might find the server doesn't like that. In this case just try to find out what media type the server is expecting and set the Headers.ContentType of the StringContent instance.
Whatever, I can create the suite by below sample, you can have a try for that:
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace CreateTestSuite
{
class Program
{
public static void Main()
{
Task t = CreateTestSuite();
Task.WaitAll(new Task[] { t });
}
private static async Task CreateTestSuite()
{
try
{
var username = "username";
var password = "password";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", username, password))));
string url = "http://server:8080/tfs/DefaultCollection/LCScrum/_apis/test/plans/212/suites/408?api-version=1.0";
var content = new StringContent("{\"suiteType\":\"StaticTestSuite\",\"name\":\"Module3\"}", Encoding.UTF8, "application/json");
using (HttpResponseMessage response = client.PostAsync(url, content).Result)
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}

SAPUI5 - OData is not defined

I am trying send some data to sap gateway service.
I am using this example the method "save", but when I try do it in my code I get an error "OData is not defined"
Below is the method when I try do it.
handleConfirmationMessageBoxPress: function(oEvent) {
var bCompact = !!this.getView().$().closest(".sapUiSizeCompact").length;
MessageBox.confirm(
"Deseja confirmar a transferência?", {
icon: sap.m.MessageBox.Icon.SUCCESS,
title: "Confirmar",
actions: [sap.m.MessageBox.Action.OK, sap.m.MessageBox.Action.CANCEL],
onClose: function(oAction) {
if (oAction == "OK") {
var oParameters = {};
oParameters.loginfrom = this.getView().byId("multiInput").getValue();
oParameters.loginfrom = this.getView().byId("loginPara").getValue();
oParameters.loginfrom = this.getView().byId("datade").getValue();
oParameters.loginfrom = this.getView().byId("datapara").getValue();
OData.request({
requestUri : "http://<host name>:<port no>/sap/opu/odata/sap/ZMM_EMP_SRV/EmployeeSet",
method : "GET",
headers : {...}
},
function(data, response) {
...
var oHeaders = {
... };
OData.request({
requestUri : "http://<host name>:<port no>/sap/opu/odata/sap/ZMM_EMP_SRV/EmployeeSet",
method : "POST",
headers : oHeaders,
data:oParameters
},
function(data,request) {
MessageToast.show("Transferência realizada!");
location.reload(true);
}, function(err) {
MessageToast.show("A transferência falhou!");
});
}, function(err) {
var request = err.request;
var response = err.response;
alert("Error in Get — Request " + request + " Response " + response);
});
} else {
...
You are attempting to use the OData global object from the datajs library. This library is indeed shipped with OpenUI5, but IMO you should not use it directly (but use the methods of the OData model; there is no real guarantee that UI5 will continue shipping this third-party library in the future).
You are most likely getting the error because the library was not yet loaded by UI5. Libraries are generally lazily loaded by UI5, so you will have to request that UI5 loads it for you (in the tutorial that you have linked, it was loaded behind the scenes by the OData model). To do this, you can either use jQuery.sap.require (jQuery.sap.require("sap.ui.thirdparty.datajs")) or list the dependency inside your sap.ui.define call at the beginning of the controller (e.g. sap.ui.define(['sap/ui/thirdparty/datajs'], function(datajs){...})).
Later edit: you can also use the jQuery.sap.require("sap.ui.model.odata.datajs"); call, but the module was moved from there and it would effectively redirect you to the new location.
this is a very old example, and the used old techniques.
You should add this line to your code:
jQuery.sap.require("sap.ui.model.odata.datajs");
This should solve your oData is undefined problem.
In general you should read newer examples where the read() function of the odata model is used.

Resources