Create Item in Dynamics 365 Business Central using Rest API - c#

I am trying to consume Microsoft Dynamics Business Central Rest API, to create item using following endpoint:
https://api.businesscentral.dynamics.com/v1.0/mydomain.com/api/v1.0/companies({id})/items
Following is my code:
string requestBody = JsonConvert.SerializeObject(itemBodyValues);
string url = "https://api.businesscentral.dynamics.com/v1.0/mydomain.com/api/v1.0/companies({id})/items";
string encodedCredentials = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(userName + ":" + WebServiceAccessKey));
HttpWebRequest endpointRequest = (HttpWebRequest)HttpWebRequest.Create(url);
endpointRequest.ContentType = "application/json";
endpointRequest.Method = "POST";
//endpointRequest.Accept = "application/json;odata=verbose";
using (var streamWriter = new StreamWriter(endpointRequest.GetRequestStream()))
{
streamWriter.Write(requestBody);
}
endpointRequest.Headers.Add("Authorization", "Basic " + encodedCredentials);
HttpWebResponse endpointResponse = (HttpWebResponse)endpointRequest.GetResponse();
Following is the request body (copied it from here):
{
"number": "1896-S",
"displayName": "ATHENS Desk",
"type": "Inventory",
"blocked": false,
"baseUnitOfMeasure": {
"unitCode": "PCS", //Unit of measure with this code exists in dynamics BC
"unitName": "Piece",
"symbol": "",
"unitConversion": null
},
"gtin": "",
"itemCategory": {
"categoryId": "TABLE", //Item category with this code exists in dynamics BC
"description": "Assorted Tables"
},
"inventory": 0,
"unitPrice": 1000.8,
"priceIncludesTax": false,
"unitCost": 780.7,
"taxGroupCode": "FURNITURE"
}
When i try to execute the code, then at endpointRequest.GetResponse(); i get the following error:
The remote server returned an error: (400) Bad Request.'
I tried creating item in postman (basic authentication), with same URL and request body and error is:
{
"error": {
"code": "BadRequest",
"message": "Does not support untyped value in non-open type. CorrelationId: 4bc23d7b-f6b3-4eca-ab62-6fb7d37e23ac."
}
}
It is important to note that item successfully gets created when i exclude baseUnitOfMeasure and itemCategory properties from request body. But including these properties causes the error. As i researched for the issue above, from different sources i found that, such issue appears when a field/property is mistyped. I am copying the request body from Microsoft document as mentioned above so i am clueless that which field is causing the issue. Please help me resolve this. Thanks

For Future visitors/readers:
It took some R&D to let me know that the request body that can be found in Microsoft Document actually contains some wrong property name.
we can go to https://api.businesscentral.dynamics.com/v1.0/[your domain]/api/v1.0/$metadata where after authentication we get the XML that specifies the property names that should be used. So, the request body that i found working is:
{
"number": "1836-S",
"displayName": "ATHENS Desk",
"type": "Inventory",
"blocked": false,
"baseUnitOfMeasure": {
"code": "PCS",
"displayName": "Piece",
"symbol": "",
"unitConversion": null
},
"gtin": "",
"itemCategory": {
"code": "TABLE", //make sure item category with this code doesn't already exists
"displayName": "Assorted Tables"
},
"inventory": 0,
"unitPrice": 1000.8,
"priceIncludesTax": false,
"unitCost": 780.7,
"taxGroupCode": ""
}

BadRequest is usually a bad datatype or json that is in the request body is not what is expected by the server side. I don't recommend adding any fields to the request body that are null this can also cause bad request.
My question is where is the field weight or net_weight on the item card to update via the rest api? Do you have to build a custom api just to include a field in the table not visible in the api to update?

Related

C# Firestore API POST - Cannot find field

I have hit a bit of a brick wall here. I am working in client side c# code and using the firestore API. I cannot post a document (with data) in. I am able to create a document by removing the line ".AddJsonBody(testdata)" below.
CODE
string url = "https://firestore.googleapis.com/v1/projects/<MYPROJECT>/databases/(default)/documents/Users";
var client = new RestClient(url);
string testdata = "{\"foo\":\"bar\"}";
var request = new RestRequest()
.AddJsonBody(testdata);
var response = await client.ExecutePostAsync(request);
ERROR
{
"error": {
"code": 400,
"message": "Invalid JSON payload received. Unknown name "foo" at 'document': Cannot find field.",
"status": "INVALID_ARGUMENT",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "document",
"description": "Invalid JSON payload received. Unknown name "foo" at 'document': Cannot find field."
}
]
}
]
}
}
I have tried using document:commit. I have tried various different methods of parsing the data, all keep getting similar errors.

.net 6 web api - bad "Content-Type" property in request body - return 400 status instead of 500

I have an Attachments json array in the request payload, and each attachment has three properties:
Name - string and holds the name of the file to be attached.
ContentType - System.Net.Mime.ContentType and holds the mediatype info of the file and
FileData - string and holds base64encoded string of contents of the file/attachment.
Whenever ContentType is null/empty I see that the control does not even hit the controller, but the API is sending a 500 internal server error, and the error message in the response is not helpful for the client:
Message: This property cannot be set to an empty string. (Parameter 'value')
Is there a way to let the API pass through whatever value is entered for this property(mediaType in ContentType) and I would like to do the validation in the API for this property and throw a 400 with a proper message. Below is the request body that is causing above error:
"Attachments": [
{
"Name": "Something.txt",
"ContentType": {
"Boundary": null,
"CharSet": null,
"MediaType": "", //this is causing the above error and I would like this one to be a bad request not a internal server error.
"Name": "SampleContentType",
"Parameters": [
{
"Key": "name",
"Value": "SampleContentType"
}
]
},
"FileData": "some base64 encoded string here"
}
]
One solution to this is to use a custom ContentType model which mimics the one being used, do the validation then map it to the original one, but I want to understand why the API is throwing 500, before it even hits the controller.
Use the ConsumesAttribute to let the framework check it.
[Consumes("multipart/form-data")] // example
public async Task<IActionResult> MyAction { ... }
This will return a 415 by default if the Consumes criteria is not met.

Parsing JSON Using Newtonsoft.Json Without Knowing the Structure

I'm working on a project that involves automating API calls using a Swagger Definition. I download the swagger.json file. The structure of the JSON Object I need to parse is not consistent. When parsing paths, there are a list of objects, then within that they have the methods that can be used for that specific path. I can retrieve just the path using various string methods but my question was, is there a good way to parse json if the JSON is structured in such a way that it does not have a firm key? Here is an example of what I mean:
{"/user": {
"post": {
"tags": [
"user"
],
"summary": "Create user",
"description": "This can only be done by the logged in user.",
"operationId": "createUser",
"consumes": [
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"parameters": [
{
"in": "body",
"name": "body",
"description": "Created user object",
"required": true,
"schema": {
"$ref": "#/definitions/User"
}
}
],
"responses": {
"default": {
"description": "successful operation"
}
}
}
}
If I wanted to just parse that path and retrieve the method object how could I go about that considering sometimes the object will be "post" or sometimes it will be "get", "put", etc depending on what is allowable for the path.
JObject jsonResp = swaggerDownload();
JObject paths = (JObject)jsonResp["paths"];
foreach (var i in paths)
{
string pathToString = i.ToString();
var shaveSomethings = pathToString.Substring(1, something.Length - 2);
var pathAndJson = shaveSomethings.Split(new[] { ',' }, 2);
string correctJsonStructure = "{\"" + pathAndJson[0] + "\":" + pathAndJson[1] + "}";
JObject bd = JObject.Parse(correctJsonStructure);
//dynamic pathsTest = JsonConvert.DeserializeObject<dynamic>(correctJsonStructure);
//JObject result = JsonConvert.DeserializeObject<JObject>(correctJsonStructure);
//Console.WriteLine(bd["/user"]);
}
The swagger.json file should have full definition of each entity that endpoints return. You can follow How to create Rest API client to get a working client.
I've dealt with an API where responses didn't always match the definition. I saved all responses to a store/log first and then would try to de-serialize JSON. In case of an exception I would go back to store/log and see what was different and update my code to accommodate for the change. After few iterations there were no new changes and the ordeal was over.
Hope that helps.

MYOB Essentials Invoice API unable to push invoice

I am using RestSharp to push an invoice to MYOB.
RestClient myobPostInvoicesClient = new RestClient("https://api.myob.com/");
RestRequest myobPostInvoicesRequest = new RestRequest("au/essentials/businesses/" + business_uid + "/sale/invoices", Method.POST);
myobPostInvoicesRequest.AddHeader("Authorization", "Bearer " + access_token);
myobPostInvoicesRequest.AddHeader("x-myobapi-key", clientId);
myobPostInvoicesRequest.AddHeader("x-myobapi-version", "v0");
myobPostInvoicesRequest.AddHeader("Content-Type", "application/json");
The JSON I am sending to the endpoint is as below
{{
"contact": {
"uid": "26939970"
},
"invoiceNumber": "IV00000000082",
"issueDate": "2020-06-07T09:00:00",
"dueDate": "2020-07-07T09:00:00",
"gstInclusive": "true",
"status": "Open",
"lines": [
{
"unitOfMeasure": "Qty",
"quantity": 5.0,
"unitPrice": 1000.0,
"total": 5000.0,
"taxType": {
"uid": "10"
},
"account": {
"uid": "9"
},
"description": "Test Description"
}
]
}}
The Response I am getting back from the MYOB Invoice API endpoint is
"{\"errors\":[{\"field\":\"\",\"message\":\"Forbidden\",\"code\":\"403\"}]}"
The access token and client id are both valid and I am following the structure of the Invoice based on the below link
https://developer.myob.com/api/essentials-accounting/endpoints/sale/invoices/
The ones I have included in the request where the fields that were previously marked as required but MYOB have modified the UI.
Just for reference I can GET contacts, accounts and taxtypes from MYOB, just getting the Forbidden 403 message back trying to POST an Invoice.
Any help you could provide would be very much appreciated.
If you are getting 403 Forbidden, you need to check the permissions on the account that you are using to make the post call.
See here to read about the permissions of the account
Except from link above
How do I check a user's access permissions
To find out exactly what rights the current user has, and to ensure they have the right permissions for your application to function correctly make a GET request to the {{company_file_uri}}/{{company_file_id}}/CurrentUser endpoint.
Following response tells you what permissions the user has on each url
{
"UserAccess": [
{
"ResourcePath": "https://{{company_file_uri}}/{{company_file_id}}/Banking/BankAccount/",
"Access": [
"GET"
]
},
{
"ResourcePath": "https://{{company_file_uri}}/{{company_file_id}}/Banking/ReceiveMoneyTxn/",
"Access": [
"GET",
"POST",
"PUT",
"DELETE"
]
},
...
]
}

Asp.net Mvc receives a complex object as a list of key/value pair array

I have two asp.net mvc apps that am trying to get to communicate with each other by sending a complex serialized object from one of the app to the other.
But when the data reaches the other end, the object comes in as a list of array of key-value pair. Below are the details of the what am trying to send/recieve.
Here is the code that am using to send the json object
using (var hc = new HttpClient())
{
hc.DefaultRequestHeaders.Accept.Clear();
hc.DefaultRequestHeaders.Accept.Add(Media);
var dict = new Dictionary<string, DejaVuObject> {{"Entity", obj}};
var strinified = JsonConvert.SerializeObject(dict);
var stringContent = new StringContent(strinified, Encoding.UTF8, "application/json");
var response = await hc.PostAsync(url, stringContent);
return response;
}
The receiving method signature
[HttpPost]
public ActionResult RecieveEntity(Dictionary<string, object> post)
Here is what am sending
{
"Main Service ID": "1",
"Node ID": "",
"Parameters": [
{
"Name": "firstname",
"Validation": {
"Entity": 0,
"EntityListFilter": "",
"IsNotEditable": false,
"IsPrimaryIdentifier": false,
"IsRequired": true,
"IsUnique": false,
"Parameter Format": 0,
"ParameterMode": ""
}
}
],
"CustomEvents": [
{
"Description": "event description",
"Message": "new message",
"MilestoneCondition": "milestone information.",
"Name": "new message",
"TheFields": []
}
],
"Processings String": "Action failed.[TN01-31:Action failed]"
}
Here is what am receiving
{
"Processings String": "Action failed.[TC01-71:Action failed while processing the request.]::Action succeeded.[TC01-54:Processing this command was successful.]",
"Parameters": "[Name, Firstname][Validation, ][Key, 2e431711-2ba9-40ef-985e-dbfa8c13a932][isrequired, True][fieldname, ][Name, Lastname][Validation, ][Key, be4de2d6-d39e-44fa-8f31-b4b0964f82da]",
"CustomEvents": "[Description, Processing this command was successful][Message, Action suceeded][MilestoneCondition, When command processing suceeds.][Name, Action suceeded][TheFields, System.Collections.Generic.List`1[System.Dynamic.DejaVuObject]]",
"Main Service ID": "1"
}
If you look properly in what comes into the other system, you will find that the of most of the inner object is an array of key/value pair instead of ordinary array of objects that was sent. What am I doing wrong, and how do i go about correcting it?
Its the fact that you're using a dictionary type to receive the content. At the point the data packet is sent from the app1 its as per your definition. So updating app1 isn't going to help you.
As #Krishna has suggested update the signature to use dynamic, or you can extend your current app2 method to deal with the new interface.
Do you have an example of the data packet from one of the existing integrated apps?

Categories

Resources