Right now, the Ember Data RESTAdapter is making this call:
GET /workshops
I want to make this call:
POST /scripts/server/api_call.php
{
"http_verb": "GET",
"endpoint": "special_namespace/workshops",
"data": {}
}
I'm doing stuff like session management, authorization, and OAuth signing in the api_call.php script, which makes the actual RESTful request and returns the result.
What are the steps to extend the RESTAdapter to do this?
I think you'd have to override the serialize and buildURL methods. You could also override the find call directly and bypass all of the unnecessary Ember-Data calls.
But in my opinion, the RESTAdapter is incredibly complicated nowadays. Given that your API is so different, I think you would be better off writing your own adapter from scratch. My custom REST adapter is about 100 lines long, and 20 of those are just $.ajax options. For instance, your find call could be as easy as:
find: function(store, type, id) {
return new Ember.RSVP.Promise(function(resolve) {
var data = {
http_verb: 'GET',
endpoint: 'special_namespace/' + type.typeKey.pluralize(),
data: {}
};
$.post('/scripts/server/api_call.php', data, function(response) {
resolve(response);
});
});
}
Related
I have a API that accepts a submission request (that when accepted to return http code 202 Accepted), for some workflow to approve, and then the caller to retrieve the result later.
The URL should be returned on successful submission and I am trying to use AcceptedAtActionResult with Azure Functions, however I do not understand how to use it as they don't have Controllers per my understanding, but this is one of the parameters.
Regardless and trying to use string.Empty, or the class name EnrollmentFunctions, or path enroll, the library is generating strange paths (in this case pointing to a different function in a different file i.e. location: http://localhost:7071/api/attest/c588484e-8e57-47f6-bf6c-973cfa5b9214?action=GetStatus&controller=enroll).
I am looking to return of the form location: http://localhost:7071/api/enroll/c588484e-8e57-47f6-bf6c-973cfa5b9214 (but avoid hardcoding URLs, both for maintenance and also running in dev mode on local machine).
I believe this would get more complicated if we used a cloud WAF (like cloudflare) on the front-end, in that case I presume would create my own class to build the URL (i.e. https://api-protected-by-waf.mydomain.com/enroll/bf920104-a630-4b58-97cb-cc3ae45c31d3)? I can see this then becoming a configuration issue was looking to avoid on the previous para.
Research points to creating my own IUrlHelper and possibly specifying key:values for retrival via Environment.GetEnvironmentVariable("your_key_here") (for use by my IUrlHelper implementation).
Thanks in advance on how to solve this issue or if i am using the libraries incorrectly and alternate best practice. Code of the Azure functions are below:
public class EnrollmentFunctions
{
[FunctionName("Enroll")]
public async Task<IActionResult> Enroll(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "enroll")] HttpRequest req)
{
var id = Guid.NewGuid();
return new AcceptedAtActionResult("GetStatus", "enroll", new { id = id }, id);
// return new AcceptedAtRouteResult("GetStatus", (new { id }, new { Result = id.ToString() }));
}
[FunctionName("GetStatus")]
public async Task<IActionResult> GetStatus(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "enroll/{id}")] HttpRequest req)
{
return new OkObjectResult("OK");
}
}
So let's say I have an existing application that has two endpoints /people and /pants. Calling GET /people returns:
[
{
"name":"john",
"age":37,
"pants":[
{
"color":"green",
"brand":"levis",
"size":"medium"
},
{
"color":"indigo",
"brand":"jncos",
"size":"medium-with-huge-legs"
}
]
},
{
"name":"june",
"age":23,
"pants":[
{
"color":"pink",
"brand":"gap",
"size":"small"
}
]
}
]
If i were to use Spring Data Rest and call GET /person i'd receive something like:
{
"_links":{
"next":{
"href":"http://myapp.com/people?page=1&size=20"
},
"self":{
"href":"http://myapp.com/people{&page,size,sort}",
"templated":true
},
"search":{
"href":"http://myapp.com/people/search"
}
},
"_embedded":{
"people":[
{
"name":"john",
"age":37,
"_links":{
"self":{
"href":"http://myapp.com/people/john"
},
"pants":{
"href":"http://myapp.com/people/john/pants"
}
}
},
{
"name":"june",
"age":23,
"_links":{
"self":{
"href":"http://myapp.com/people/june"
},
"pants":{
"href":"http://myapp.com/people/june/pants"
}
}
}
]
}
}
Let's say I have a bunch of existing clients that I don't want to have to change - is there any way to disable the hypermedia portions of the response in some cases (say Accept="application/json") but enable it them for others (Accept="hal+json")?
Thanks!
Updated
Okay - so it appears that much to my chagrin, what I'm looking to do is not supported. I understand why Spring Data Rest is strongly leaning toward Hypermedia... but I don't buy that providing the capability to "disable" hypermedia based on a header thus providing more options is a bad thing.
That aside, I'm a bit unsure of how to actually achieve this via my own Controllers. If I create a Controller and attempt to override the /people RequestMapping with produces = "application/json" I am able to get the "raw" json back with Accept="application/json" but if I pass Accept="application/hal+json"` I get a 406 with "Could not find acceptable representation". It looks like the SDR resource mappings aren't mapped with a content type ... any suggestions?
The short answer is, you can't use spring-data-rest without hateoas. If you want to build your web service without hateoas, you'll have to write your own controllers (which can still use spring-data repositories).
Quoting Oliver Gierke in this SO post:
Actually my whole point is: the server is just doing decent REST. If
that breaks the client, it's the client that needs to be fixed
(tweaked). So the hypermedia aspect is a fundamental one to Spring
Data REST and we're not going to back out of that. That's probably not
satisfying in your concrete situation but should answer the question
at least :). – Oliver Gierke
I have a web app which talks to my backend node.js+sails app through a socket.
Default routes for sockets use id. As example
io.socket.get('/chatroom/5')
My app doesn't authenticate users and as result I want id's to be random, so nobody can guess it. However, id's are generated by mongoDB and aren't that random.
As result, I want to use some other field (a.e. "randomId") and update routing for this model to use this field instead of id.
What's the best way to do it?
P.S. It looks like I have to use policies, but still struggling to figure out what should I do exactly.
You aren't forced to use the default blueprint routes in your app; you can always override them with custom controller methods or turn them off entirely.
The GET /chatroom/:id method automatically routes to the find action of your ChatroomController.js file. If you don't have a custom action, the blueprint action is used. So in your case, you could define something like the following in ChatroomController.js:
find: function (req, res) {
// Get the id parameter from the route
var id = req.param('id');
// Use it to look up a different field
Chatroom.find({randomId: id}).exec(function(err, chatrooms) {
if (err) {return res.serverError(err);}
// Subscribe to these rooms (optional)
Chatroom.subscribe(req, chatrooms);
// Return the room records
return res.json(chatrooms);
});
}
If you don't like the name find or the param id, you can set your own route in config/routes.js:
"GET /chatroom/:randomid": "ChatroomController.myFindActionName"
Also, re:
Default routes for sockets use id.
those routes aren't just for sockets--they respond to regular HTTP requests as well!
I created a policy. This policy converts randomId (which is passed as :id) to real id and saves it in req.options.id (which Sails will pick up).
module.exports = function(req, res, next) {
var Model = req._sails.models[req.options.model];
var randomId = req.params.all()['id'];
Model.findOne().where({ randomId: randomId }).exec(function(err, record) {
req.options.id = record.id;
return next();
});
};
And I apply this policy to findOne and update actions of my controller:
ChatRoomController: {
findOne : 'useRandomId',
update : 'useRandomId'
}
When exposing querystring parameters using GET I have the following base URL:
https://school.service.com/api/students
This will return the first 25 students.
What if I want to return a list of students based on ONE of the following criteria:
* have accepted a job
* have received a job offer
* have no job offers
The three above choices are essentially an enum.
Therefore, the query request for students who have no job offers I assume would look like:
https://school.service.com/api/students?jobOfferStatus=3
However, I'm wondering if jobOfferStatus=3 is the proper way to handle this. If so, how would I publish/provide to the clients a list of available options for that jobOfferStatus query parameter? What about other possible query parameters and their valid options? We'll have many possible query parameters like this.
I'd love to see an example of how this should be done properly. What are the best practices?
There are two main options: documenting it, or making it discoverable. A lot of APIs have documentation where they list all of the resources and parameters for reference. Otherwise, the client won't know.
You could also make it discoverable in some way by including the options in a response. For conventions on this, search for HATEOAS if you haven't already. (I'm not really knowledgeable enough about HATEOAS myself to make a suggestion.)
I will mention that "3" is not a very meaningful value for jobOfferStatus, and there's no need for the client to know that number. You can make it anything you want -- jobOfferStatus=none or even jobOffer=none. Your controller can do the work of matching that value to your enumeration. Try to design your interface to be intuitive for developers (and, of course, write good documentation).
To handle multiple query parameters, you can use optional parameters in your function:
public HttpResponseMessage GetStudents(string jobOffer = "",
string other1 = "",
string other2 = "")
{
if (jobOffer == "accepted" && other2 == "whatever") {
// return a response
}
else {
// return a different response
}
}
When the client uses parameters by those names, you can tailor your response appropriately.
You have some options to do this, let's try to help:
1) Configure a generic route to asp.net web api knows how to solve another action's name different from Get to a get method, on the App_Start\WebConfigApi.cs class, try to add this:
config.Routes.MapHttpRoute("DefaultApiWithActionAndId",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
Using it, you can have diferent methods on the api controller:
// request: get
// url: api/Students/GetStudents
public HttpResponseMessage GetStudents()
{
return Request.CreateResponse(...);
}
// request: get
// url: api/Students/GetStudentsWithJobOffer
public HttpResponseMessage GetStudentsWithJobOffer()
{
return Request.CreateResponse(...);
}
// request: get
// url: api/Students/GetStudentsAcceptedJob
public HttpResponseMessage GetStudentsAcceptedJob()
{
return Request.CreateResponse(...);
}
2) Use a simple parameter on the Get method:
// request: get
// url: api/Students?jobOfferStatus=1
public HttpResponseMessage GetStudents(int jobOfferStatus)
{
// use jobOfferStatus parameter to fill some list
return Request.CreateResponse(...);
}
3) Use a simple method with a parameter named id, to get a default friendly url by asp.net mvc web api.
// request: get
// url: api/Students/1
public HttpResponseMessage GetStudents(int id)
{
// use the id parameter to fill some list
return Request.CreateResponse(...);
}
I am somewhat confused of the way to achieve the two-way data binding when posting to my server.
I defined my resource like this:
angular.module('todoServices', ['ngResource']).
factory('Todo', function($resource){
return $resource('api/v1-0/todos/:todoId', {}, {
query: {method: 'GET', params:{todoId:''}, isArray: true},
save: {method: 'POST', isArray: true}
});
})
and I pass the Todo resource to my controller as a dependency.
Then in my controller I have a method to add a new Todo item to my list:
$scope.addTodo = function() {
var savedModel = new Todo();
savedModel.title = $scope.title;
savedModel.description = $scope.description,
//...
savedModel.$save();
$scope.todos.push(savedModel);
}
This works as far as my todo appears in the list, the call to the server works and the item is added in my database.
However, since when I push it to my list, it does not have an ID, yet. The ID is generated by an auto-increment ID in my MySQL database.
My server returns the object in JSON format, so I assume, I have to specify some sort of callback function to get the data-binding to work?
What exactly do I need to do, so my todo which has been added is updated with the correct ID once my server returns the data?
Simply assign the returned object to the savedModel object. Since calls to resources are asynchronous and return a promise, you should use the success function this way:
savedModel.$save(
function success(savedModel) {
$scope.todos.push(savedModel);
});
By the way, check the isArray property of the save method, normally should be false.