How to send a POST request to OData v4 ActionImport with SAPUI5 - sapui5

I would like to send a POST request to an OData v4 ActionImport. How can I achieve this in an SAPUI5 environment? I had a look at the v4 ODataModel and ODataContextBinding. There are methods for execute an ActionImport but i dont know how to set the body then.
Generally asking: How should I submit OData requests that should not necessarily be bound to the UI? For example, if I just want to query a value from the backend or send a file to the backend. Right now, I create an ODataContextBinding and call the execute/requestObject method but i think that this might not be the best approach (Also i cant set the request body this way). Maybe it might be better to make a direct ajax request?
Thanks in advance!

I stumbled on your question because I had the exact same problem. I'm providing my solution in case it helps someone else.
onValidate: function(oEvent) {
var oModel = this.getModel("reportService");
var oActivityCreateContext = this.getCreateContext();
var oActionODataContextBinding = oModel.bindContext("/validateActivity(...)");
oActionODataContextBinding.setParameter("activity", oActivityCreateContext.getObject())
oActionODataContextBinding.execute().then(
function() {
var oActionContext = oActionODataContextBinding.getBoundContext();
console.table(oActionContext.getObject().value);
}.bind(this)
);
}
The model "reportService" is a sap.ui.model.odata.v4.ODataModel. The function call is unbound and is declared this way in my service.cds file:
action validateActivity(activity : Activities) returns many rm.ValidationMessage;
The oActionContext.getObject().value contains the response to my function call.
The key here is the setParameter that sets the activity on the payload. Here's what the resulting request could look like:
POST http://localhost:8080/api/ReportService/validateActivity
Content-Type: application/json
{"activity": {
"activityNumber": 1,
"report_ID": "a3558fce-76bc-49a9-ae23-bd5566fb3bc6",
"job_code": "160",
"learningPeriod": 1,
"salaryAnnex": "D3",
"workingRegion_code": "08",
"unionName": "CSD",
"local": "Local 123",
"nbWeeksWorked": 8,
"nbHourSimple": 110,
"nbHourTimeAndHalf": 5,
"nbHourDouble": 0,
"sin": "111222333",
}}
I hope this will help others who are struggling to do this.
Regards

Related

HttpClient append parameter object to GET request

I'm quite the noob using Ionic or Angular for that matter. So as a cheat sheet I'm using the ionic-super-starter template (link below).
I am trying to make a get request to my API and it works just find if I'm doing it like this:
this.api.get('user/'+this.user.userId+'/entries?include=stuff&access_token=TOKEN');
but when I put the url params into an object it stops working:
let options = {
'include':'stuff',
'access_token':'TOKEN'
}
this.api.get('user/'+this.user.userId+'/entries', options);
The only error I get is "Unauthorized Request" since the options object including the access token was not appended to the url.
In the ionic-super-starter template the providers/api/api.ts calls .set() for each key in my params object:
if (params) {
reqOpts.params = new HttpParams();
for (let k in params) {
reqOpts.params.set(k, params[k]);
}
}
but according to Angular University this is not possible since "HTTPParams is immutable".
If it really was wrong to do this, I don't believe it would be in the ionic template. Nor would I believe that I would be the first person to come across this issue.
However, I am stuck here so any help would be appreciated.
Link to Angular University:
https://blog.angular-university.io/angular-http/#httprequestparameters
Link to ionic-super-starter:
https://github.com/ionic-team/starters/tree/master/ionic-angular/official/super
I think I figured it out myself:
if I write (in my src/providers/api/api.ts)
reqOpts.params = reqOpts.params.append(k, params[k]);
instead of
reqOpts.params.set(k, params[k]);
it works.
if you are using a loopback API as I am you might have nested objects like:
let options = {
"filter": {
"order": "date DESC"
},
"access_token":this.user._accessToken
};
this won’t work. try instead:
let options = {
"filter": '{"order":"date DESC"}',
"access_token":this.user._accessToken
};

PUT Request not happening at all in Fantom

I am having some trouble with PUT requests to the google sheets api.
I have this code
spreadsheet_inputer := WebClient(`$google_sheet_URI_cells/R3C6?access_token=$accesstoken`)
xml_test := XDoc{
XElem("entry")
{
addAttr("xmlns","http://www.w3.org/2005/Atom")
addAttr("xmlns:gs","http://schemas.google.com/spreadsheets/2006")
XElem("id") { XText("https://spreadsheets.google.com/feeds/cells/$spreadsheet_id/1/private/full/R3C6?access_token=$accesstoken"), },
XElem("link") { addAttr("rel","edit");addAttr("type","application/atom+xml");addAttr("href","https://spreadsheets.google.com/feeds/cells/$spreadsheet_id/1/private/full/R3C6?access_token=$accesstoken"); },
XElem("gs:cell") { addAttr("row","3");addAttr("col","6");addAttr("inputValue","testing 123"); },
},
}
spreadsheet_inputer.reqHeaders["If-match"] = "*"
spreadsheet_inputer.reqHeaders["Content-Type"] = "application/atom+xml"
spreadsheet_inputer.reqMethod = "PUT"
spreadsheet_inputer.writeReq
spreadsheet_inputer.reqOut.writeXml(xml_test.writeToStr).close
echo(spreadsheet_inputer.resStr)
Right now it returns
sys::IOErr: No input stream for response 0
at the echo statement.
I have all the necessary data (at least i'm pretty sure) and it works here https://developers.google.com/oauthplayground/
Just to note, it does not accurately update the calendars.
EDIT: I had it return the response code and it was a 0, any pointers on what this means from the google sheets api? Or the fantom webclient?
WebClient.resCode is a non-nullable Int so it is 0 by default hence the problem would be either the request not being sent or the response not being read.
As you are obviously writing the request, the problem should the latter. Try calling WebClient.readRes() before resStr.
This readRes()
Read the response status line and response headers. This method may be called after the request has been written via writeReq and reqOut. Once this method completes the response status and headers are available. If there is a response body, it is available for reading via resIn. Throw IOErr if there is a network or protocol error. Return this.
Try this:
echo(spreadsheet_inputer.readRes.resStr)
I suspect the following line will also cause you problems:
spreadsheet_inputer.reqOut.writeXml(xml_test.writeToStr).close
becasue writeXml() escapes the string to be XML safe, whereas you'll want to just print the string. Try this:
spreadsheet_inputer.reqOut.writeChars(xml_test.writeToStr).close

Getting a 204 No Content response when I use restSetResponse in Coldfusion 10 REST

I am trying to use restSetResponse to set my own status and content as described in the docs here:
http://help.adobe.com/en_US/ColdFusion/10.0/Developing/WSe61e35da8d318518-5719eac51353e6bb244-7fec.html
Here is my REST method:
remote any function test() httpmethod="get" {
var response = {
status: 400,
content: 'something is wrong'
};
restSetResponse(response);
}
Any help would be greatly appreciated. Thanks!
In the link you provided, note the first numbered bullet under the "Send custom success responses using restSetResponse" heading. If you define the CFC as REST service, you need to ensure the returnType is set to "void". In your example, you have your returnType set to "any".

What's the best way to handle a REST API's 'create' response in Backbone.js

I'm using backbone.js to interact with a REST API that, when posting to it to create a new resource, responds with a status of 201, a 'Location' header pointing to the resource's URI, but an empty body.
When I create a new model at the moment, its successful, but the local representation of the model only contains the properties I explicitly set, not any of the properties that would be set on the server (created_date, etc.)
From what I understand, Backbone would update its representation of the model with data in the body, if there were any. But, since there isn't, it doesn't.
So, clearly, I need to use the location in the Location header to update the model, but what's the best way to do this.
My current mindset is that I would have to parse the url from the header, split out the id, set the id for the model, then tell the model to fetch().
This seems really messy. Is there a cleaner way to do it?
I have some influence over the API. Is the best solution to try to get the API author to return the new model as the body of the response (keeping the 201 and the location header as well)?
Thanks!
Sounds like you will have to do a little customization.
Perhaps override the parse method and url method of your model class inherited from
Backbone.Model.
The inherited functions are:
url : function() {
var base = getUrl(this.collection);
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + this.id;
},
parse : function(resp) {
return resp;
},
and you could try something like:
parse: function(resp, xhr) {
this._url = xhr.getResponseHeader('location')
return resp
}
url: function() {
return this._url
}
Yes, backbone.js really wants the result of a save (be it PUT or POST) to be a parseable body which can be used to update the model. If, as you say, you have influence over the API, you should see if you can arrange for the content body to contain the resource attributes.
As you point out, its makes little sense to make a second over-the-wire call to fully materialize the model.
It may be that a status code of 200 is more appropriate. Purists may believe that a 201 status code implies only a location is returned and not the entity. Clearly, that doesn't make sense in this case.
With Backbone 0.9.9, I couldn't get the accepted answer to work. The signature of the parse function seems to have changed in an older version, and the xhr object is no longer available in the function signature.
This is an example of what I did, to make it work with Backbone v0.9.9 and jQuery 1.8.3 (using a Deferred Object/Promise), relying on the jqXHR object returned by Backbone.Model.save() :
window.CompanyView = Backbone.View.extend({
// ... omitted other functions...
// Invoked on a form submit
createCompany: function(event) {
event.preventDefault();
// Store a reference to the model for use in the promise
var model = this.model;
// Backbone.Model.save returns a jqXHR object
var xhr = model.save();
xhr.done(function(resp, status, xhr) {
if (!model.get("id") && status == "success" && xhr.status == 201) {
var location = xhr.getResponseHeader("location");
if (location) {
// The REST API sends back a Location header of format http://foo/rest/companys/id
// Split and obtain the last fragment
var fragments = location.split("/");
var id = fragments[fragments.length - 1];
// Set the id attribute of the Backbone model. This also updates the id property
model.set("id", id);
app.navigate('companys/' + model.id, {trigger: true});
}
}
});
}
});
I did not use the success callback that could be specified in the options hash provided to the Backbone.Model.save function, since that callback is invoked before the XHR response is received. That is, it is pointless to store a reference to the jqXHR object and use it in the success callback, since the jqXHR would not contain any response headers (yet) when the callback is invoked.
Another other to solve this would be to write a custom Backbone.sync implementation, but I didn't prefer this approach.

How to construct a REST API that takes an array of id's for the resources

I am building a REST API for my project. The API for getting a given user's INFO is:
api.com/users/[USER-ID]
I would like to also allow the client to pass in a list of user IDs. How can I construct the API so that it is RESTful and takes in a list of user ID's?
If you are passing all your parameters on the URL, then probably comma separated values would be the best choice. Then you would have an URL template like the following:
api.com/users?id=id1,id2,id3,id4,id5
api.com/users?id=id1,id2,id3,id4,id5
api.com/users?ids[]=id1&ids[]=id2&ids[]=id3&ids[]=id4&ids[]=id5
IMO, above calls does not looks RESTful, however these are quick and efficient workaround (y). But length of the URL is limited by webserver, eg tomcat.
RESTful attempt:
POST http://example.com/api/batchtask
[
{
method : "GET",
headers : [..],
url : "/users/id1"
},
{
method : "GET",
headers : [..],
url : "/users/id2"
}
]
Server will reply URI of newly created batchtask resource.
201 Created
Location: "http://example.com/api/batchtask/1254"
Now client can fetch batch response or task progress by polling
GET http://example.com/api/batchtask/1254
This is how others attempted to solve this issue:
Google Drive
Facebook
Microsoft
Subbu Allamaraju
I find another way of doing the same thing by using #PathParam. Here is the code sample.
#GET
#Path("data/xml/{Ids}")
#Produces("application/xml")
public Object getData(#PathParam("zrssIds") String Ids)
{
System.out.println("zrssIds = " + Ids);
//Here you need to use String tokenizer to make the array from the string.
}
Call the service by using following url.
http://localhost:8080/MyServices/resources/cm/data/xml/12,13,56,76
where
http://localhost:8080/[War File Name]/[Servlet Mapping]/[Class Path]/data/xml/12,13,56,76
As much as I prefer this approach:-
api.com/users?id=id1,id2,id3,id4,id5
The correct way is
api.com/users?ids[]=id1&ids[]=id2&ids[]=id3&ids[]=id4&ids[]=id5
or
api.com/users?ids=id1&ids=id2&ids=id3&ids=id4&ids=id5
This is how rack does it. This is how php does it. This is how node does it as well...
There seems to be a few ways to achieve this. I'd like to offer how I solve it:
GET /users/<id>[,id,...]
It does have limitation on the amount of ids that can be specified because of URI-length limits - which I find a good thing as to avoid abuse of the endpoint.
I prefer to use path parameters for IDs and keep querystring params dedicated to filters. It maintains RESTful-ness by ensuring the document responding at the URI can still be considered a resource and could still be cached (although there are some hoops to jump to cache it effectively).
I'm interested in comments in my hunt for the ideal solution to this form :)
You can build a Rest API or a restful project using ASP.NET MVC and return data as a JSON.
An example controller function would be:
public JsonpResult GetUsers(string userIds)
{
var values = JsonConvert.DeserializeObject<List<int>>(userIds);
var users = _userRepository.GetAllUsersByIds(userIds);
var collection = users.Select(user => new { id = user.Id, fullname = user.FirstName +" "+ user.LastName });
var result = new { users = collection };
return this.Jsonp(result);
}
public IQueryable<User> GetAllUsersByIds(List<int> ids)
{
return _db.Users.Where(c=> ids.Contains(c.Id));
}
Then you just call the GetUsers function via a regular AJAX function supplying the array of Ids(in this case I am using jQuery stringify to send the array as string and dematerialize it back in the controller but you can just send the array of ints and receive it as an array of int's in the controller). I've build an entire Restful API using ASP.NET MVC that returns the data as cross domain json and that can be used from any app. That of course if you can use ASP.NET MVC.
function GetUsers()
{
var link = '<%= ResolveUrl("~")%>users?callback=?';
var userIds = [];
$('#multiselect :selected').each(function (i, selected) {
userIds[i] = $(selected).val();
});
$.ajax({
url: link,
traditional: true,
data: { 'userIds': JSON.stringify(userIds) },
dataType: "jsonp",
jsonpCallback: "refreshUsers"
});
}