simple rest data provider won't accept Odata data - odata

So the API I am using is using ODATA (https://www.odata.org/). As a result this is what it looks like when it's returned
```
{"#odata.context":"http://localhost:5001/api/$metadata#apemp",
"value":[
{"EmpID":1,
"Abbr":"Admin",
"BadgeNo":null,
"ColorRef":0,
"ContactMethodID":null,
"DateHired":"2018-05-25T16:42:57-05:00"}
]```
our data provider looks like this
import { stringify } from 'query-string';
import {
fetchUtils,
GET_LIST,
GET_ONE,
GET_MANY,
GET_MANY_REFERENCE,
CREATE,
UPDATE,
UPDATE_MANY,
DELETE,
DELETE_MANY
} from 'react-admin';
...
const convertHTTPResponse = (response, type, resource, params) => {
const { headers, json } = response;
switch (type) {
case GET_LIST:
return { data: json };
case GET_MANY_REFERENCE:
if (!headers.has('content-range')) {
throw new Error(
'The Content-Range header is missing in the HTTP Response. The simple REST data provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?'
);
}
return {
data: json,
total: parseInt(
headers
.get('content-range')
.split('/')
.pop(),
10
)
};
case CREATE:
return { data: { ...params.data, id: json.id } };
default:
return { data: json };
}
};
return (type, resource, params) => {
// simple-rest doesn't handle filters on UPDATE route, so we fallback to calling UPDATE n times instead
if (type === UPDATE_MANY) {
return Promise.all(
params.ids.map(id =>
httpClient(`${apiUrl}/${resource}/${id}`, {
method: 'PUT',
body: JSON.stringify(params.data)
})
)
).then(responses => ({
data: responses.map(response => response.json)
}));
}
// simple-rest doesn't handle filters on DELETE route, so we fallback to calling DELETE n times instead
if (type === DELETE_MANY) {
return Promise.all(
params.ids.map(id =>
httpClient(`${apiUrl}/${resource}/${id}`, {
method: 'DELETE'
})
)
).then(responses => ({
data: responses.map(response => response.json)
}));
}
const { url, options } = convertDataRequestToHTTP(type, resource, params);
return httpClient(url, options).then(response =>
convertHTTPResponse(response, type, resource, params)
);
};
};
so right now when I point that data provider to api endpoints it doesn't look the way the code is formatted.
I get the error
"The response to 'GET_LIST' must be like { data : [...] }, but the received data is not an array. The dataProvider is probably wrong for 'GET_LIST'"
I have used this dataprovider extensively with our previous API which returned data slightly differently.
since odata returns an object with the context and url as the items and the value with an array as the second item it doesn't work.
I really just need the array but don't know what I should be writing to get that.

I am also implementing DataProvider for OData and what you need to do is that you have to "wrap" your response data stored in value with data object.
But the array is in value prop and you can't just assert return { data: json }; like you do, because it expects object like {data: [0: {prop1: "hello", prop2: "world"}, 1: { ... }]} and you return this:
{
"#odata.context":"http://localhost:5001/api/$metadata#apemp",
"value":[
{
"EmpID":1,
"Abbr":"Admin",
"BadgeNo":null,
"ColorRef":0,
"ContactMethodID":null,
"DateHired":"2018-05-25T16:42:57-05:00"
}
]
}
So what you need to do is to assign value prop of response json, eg:
return { data: json.value };
So its actually pretty easy to fix

Related

Reg: Angular API Return data with array index "value below was evaluated just now”

i am using Angular for front end. i am using the API request to bring languages and phrases. return data is pushed into declared array variable.
```public data = [];
constructor(private http: HttpClient) {
this.getMyLang();
}
async getMyLang() {
const headers = {
headers: new HttpHeaders({
'X-API-KEY': 'xxxxxx',
})
};
const param = new FormData();
param.append('language', 'en');
await this.http.post('api url', param, headers).subscribe((data1) => {
this.data.push(data1);
});
}```
when i call this method it pushes the data. after console this variable into component am getting following results.
console Screenshot enter code here

How to pass 3 argument to backend with `POST` request?

According to backend, I require to pass 3 argument through post request, this backend function is:
public ResponseModel Post([FromBody] CourseFileUpload item, string fileName, Stream fileToUpload)
now I am trying to pass the argument like this:
uploadFile(uploadData:ModelToFileSteam):Observable<ModelToFileSteam> {
const fileName = uploadData.fileName;
console.log('file name is', fileName);
const headers = new HttpHeaders({ 'Content-Type': 'application/json', 'Access-Control-Allow-Origin':'*' });
return this.http.post<ModelToFileSteam>(environment.baseUrl+`CourseFileUpload`, uploadData.fileToUpload, uploadData.fileName, uploadData.uploadStream)
.pipe(
map(data => {
return data;
} ),
catchError(this.handleError)
)
}
But getting error, not able to pass 3 arguments at all. what is the correct way to do this?
any one help me?
I will suggest wrapping all in a single object. And send it to backend.
Or just send uploadData
return this.http.post<ModelToFileSteam>(environment.baseUrl+`CourseFileUpload`, uploadData)
.pipe(
map(data => {
return data;
} ),
catchError(this.handleError)
)
And in the backend, you can get uploadDate like req.body.uploadData
To check you can console.log(uploadData.fileName);
its my working example
this.http.post<Customer>(this.base_url + 'v1/customers', client, this.getHeaders());
Where client is customer object and this.getHeaders() is:
getHeaders() {
return {
headers: new HttpHeaders({
'Content-Type': 'application/json; charset=utf-8',
})
};
}
Good luck!

$resource returns an empty object

I use a $resource to get data from the Server calling an ASP.NET MVC controller from my angular controller. The value (of type decimal?) is correctly returned from the MVC Controller, but when the promise is resolved, its parameter is just an empty object and not the expected value.
$resource file:
angular
.module('app')
.service('datacontext', function ($rootScope, $resource) {
return {
emissions: $resource('/api/emissions/:id', { 'id': '#id', emissionId: '#emissionId' },
{
create: { method: 'POST' },
lock: { method: 'PUT', url: "/api/emissions/lock" },
getFxRate: { method: 'GET', isArray: false, url: '/api/emissions/getfxrate'}
})
}
ASP.NET MVC Controller
[HttpGet]
[Route("/api/emissions/getfxrate")]
public async Task<IHttpActionResult> GetFxRate(string currency)
{
var res = await _emissionsAppService.GetFxRateAsync(currency);
//Here I get properly a decimal value as expected. (eg. 1.07123)
return Ok(res);
}
Angular Controller:
function getRate(currency){
datacontext.emissions.getFxRate({ currency: currency})
.$promise.then(function (result) {
//Here result is just an empty object {}
}
}
I cannot understand what is missing to get back the value form the server to the client controller.
The resolved promise was empty as a JSON object is expected, while I was returning a primitive value.
I could fix it by adding the following code to my $resource. As soon as I was returning an object, I could grab the value correctly inside the promise:
getFxRate: {
method: 'GET',
isArray: false,
url: '/api/emissions/getfxrate',
transformResponse: function (data) {
return { fxRate: data };
}
}

How can I do multiple post parameters in web api?

I have a web-api, 2 tables in my SQL DB, JT and Sales. Before I add() to the database I need the poster to specify first in the uri whether he/she wants to post to JT table or Sales. My problem is that my post method only accepts one model binding, it doesn't want two like as shown on my code below. It doesn't have errors but when I post with that logic in mind it returns an error in POSTMAN that It can't bind multiple parameters ('JT' and 'Sales') to the request's content.
Here is my code:
[ResponseType(typeof(JT))]
public HttpResponseMessage PostJT(JT JT, Sales Sales, [FromUri] string tran)
{
try
{
if (ModelState.IsValid)
{
if (tran == null)
{
return Request.CreateResponse(HttpStatusCode.Unauthorized, "Unauthorized Access!");
}
else
{
switch (tran)
{
case "JT": db.JTs.Add(JT);
break;
case "sales": db.Sales_.Add(Sales);
break;
}
}
db.SaveChanges();
return Request.CreateErrorResponse(HttpStatusCode.OK, "Added!");
}
//below are just elses with return messages.
There is no direct way to pass multiple parameter but you can use the below work around
This link gives complete details on Web API passing Multiple parameter
$.ajax(
{
url: "samples/PostAlbum",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ JT: jt, Sales: sales, UserToken: userToken }),
success: function (result) {
alert(result);
}
});
[HttpPost]
public string PostAlbum(JObject jsonData)
{
try
{
if (ModelState.IsValid)
{
if (tran == null)
{
return Request.CreateResponse(HttpStatusCode.Unauthorized,
"Unauthorized Access!");
}
else
{
dynamic json = jsonData;
JObject jtObject = json.JT;
JObject salesObject = json.Sales;
var jt = jtObject.ToObject<JT>();
var sales = salesObject.ToObject<Sales>();
if (jt != null)
{
db.JTs.Add(JT);
}
else if (sales != null)
{
db.Sales_.Add(Sales);
}
}
db.SaveChanges();
return Request.CreateErrorResponse(HttpStatusCode.OK, "Added!");
}
//below are just elses with return messages.
}

How handle different results in an ajax call?

Suppose you have the following controller action
[HttpPost]
public ActionResult Save( CustomerModel model )
{
if (!ModelState.IsValid) {
//Invalid - redisplay form with errors
return PartialView("Customer", model);
}
try {
//
// ...code to save the customer here...
//
return PartialView( "ActionCompleted" );
}
catch ( Exception ex ) {
ActionErrorModel aem = new ActionErrorModel() {
Message = ex.Message
};
return PartialView( "ActionError", aem );
}
}
And suppose you call this action using jQuery:
$.ajax({
type: "post",
dataType: "html",
url: "/Customer/Save",
sync: true,
data: $("#customerForm").serialize(),
success: function(response) {
/*
??????
*/
},
error: function(response) {
}
});
I would like to be able to distinguish between the results I am getting to handle them in different ways on the client. In other words how can I understand that the action
returned the same model because has not passed validation
returned one of the views that represents error info/messages
Any suggestion?
One way to handle this is to append a custom HTTP header to indicate in which case we are falling:
[HttpPost]
public ActionResult Save( CustomerModel model )
{
if (!ModelState.IsValid) {
//Invalid - redisplay form with errors
Response.AppendHeader("MyStatus", "case 1");
return PartialView("Customer", model);
}
try {
//
// ...code to save the customer here...
//
Response.AppendHeader("MyStatus", "case 2");
return PartialView( "ActionCompleted" );
}
catch ( Exception ex ) {
ActionErrorModel aem = new ActionErrorModel() {
Message = ex.Message
};
Response.AppendHeader("MyStatus", "case 3");
return PartialView( "ActionError", aem );
}
}
And on the client side test this header:
success: function (response, status, xml) {
var myStatus = xml.getResponseHeader('MyStatus');
// Now test the value of MyStatus to determine in which case we are
}
The benefit of this is that the custom HTTP header will always be set in the response no matter what content type you've returned. It will also work with JSON, XML, ...
Remark 1: To avoid cluttering you controller action with all those Response.AppendHeader instructions you could write a custom ActionResult allowing you to directly specify the value of this header so that you simply return this.MyPartialView("Customer", model, "case 1")
Remark 2: Remove this sync: true attribute from the request because it makes my eyes hurt (in fact I think you meant async: 'false').
You could check for an element unique to that view, for example:
$.ajax({
type: "post",
dataType: "html",
url: "/Customer/Save",
sync: true,
data: $("#customerForm").serialize(),
success: function(response) {
var resp = $(response);
if($(resp.find("#customer").length) {
//customer returned
} else if($(resp.find("#completed").length) {
//completed view
} else if($(resp.find("#error").length) {
//error view
}
},
error: function(response) {
}
});

Resources