Calling an API HttpGet Action from Angular with a large amount of search criteria throws a CORS policy error - json

I have a Core 2.2 Web API project as my back-end for an Angular front-end. On one screen I allow the user to select x amount of records for processing from a data grid. Before the records can be processed I need to check the database to see if records exist in another table by passing a list of 3 fields (intelligent key) to my API. I put this list into an object array, do a Json.stringify on that object and send it to my API as a Get request. This works fine as long as I select 1-3 records. As soon as I select 4 or more records I get "Access to XMLHttpRequest at 'request url' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
My Cors policy indicates it should let anything through and I'm also confused by why 1-3 records works fine.
In my startup.cs -> ConfigureServices method I have the Cors policy defined like so:
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
In my Angular service I make this call which serializes by array (apiUrl is my url to call my API: https://localhost/api/controller)
getRecordsByIntelligentKey(intelligentKey: IntelligentKey[]): Observable<Record[]>{
const jsonObject = JSON.stringify(intelligentKey);
const url = `${apiUrl}/${jsonObject}`;
return this.http.get<Record[]>(url).pipe(
tap(_ => console.log('fetching records based on intelligent key')),
catchError(this.handleError('getRecordsByIntelligentKey', []))
);
}
In my controller GET action I deserialize my string. I mean I'd like to pass an object but I think I need to do a POST for that.
[HttpGet("{jsonObject}")]
public ActionResult<List<Commission>> GetByCustomerAndTerritoryManager(string jsonObject)
{
try
{
var intelligentKey = JsonConvert
.DeserializeObject<List<ThreeFields>>(jsonObject);
return _repo.GetRecordsByIntelligentKey(intelligentKey);
}
catch (Exception ex)
{
_logger.Error(ex, "Error retrieving records.");
throw;
}
}
Now my problem is the user could select 1000s of records. When I select over 1000 records I just get ERR_CONNECTION_RESET probably due to the querystring being way too long.
I'm thinking I need an object but everything I've researched seems to advise against doing that with a GET and using the POST request instead. Problem is, it's a restful API and I'm already using the POST request for the processing portion. I guess I could use PUT or DELETE but it just feels wrong. I'm going to wire up the PUT right after I post this question to see if it will work but ultimately I'd like to find the correct solution for this.
UPDATE: The PUT method works fine even with over 1000 records selected so I guess this will be my interim solution for now. I still feel like there's code smell and would love to use a GET but at least this allows me to proceed.

Related

How to create web api methods?

Hi I am developing one restfull wep api application. I am new to the world of web api and little confused about http verbs. My tasks is to write services using web api2. I have one table in sql server and i am trying to do basic crud operation around this table. I want to send data in json format and return data as json. For example http://localhost:26079/api/User_Creation/1 returns data in json format as expected. My task is to host above method in iis so that anyone can access that method to retrieve data. I am confused suppose if i want to insert some data to db then what would be the method? I have below code in controllera and i am able to insert data.
public void Post(Noor_Users users)
{
if (ModelState.IsValid)
{
entityObject.Noor_Users.Add(users);
int result = entityObject.SaveChanges();
}
}
When i insert data my url will be http://localhost:26079/ but how can i expose my insert data method to outside world? My requirement is as follows.
URL:/user_creation
method:post
Request:parameters such as fname,lname as json
Response:0 for success 1 for failure and data(unique id assigned to each user)
may i get some help on this? Thank for consideration.
When considering REST it is important to understand and design it as you are taking actions against a resource at the location, and not like making a remote function call.
So I would suggest to have API as -
http://localhost:26079/api/User
instead of - http://localhost:26079/api/User_Creation
In order to adhere to REST principals.
Doing this I am very clearly stating that the user of the api will be able to perform operations on the resource (which is a User in this case) using different verbs viz. GET, POST, PUT and DELETE.
See some examples on using the API -
If I need to get a user with Id 1, I would call api like, http://localhost:26079/api/User/1 with GET request
To create a new user, I would call, http://localhost:26079/api/User with POST request and send the user information in request body
To delete a user with id 1, I would call http://localhost:26079/api/User/1 with Delete request
To update a user data with Id 1, call http://localhost:26079/api/User/1 with PUT request and send the updated user information in request body
Please note here that we are using just a single endpoint to perform different operations on our User by changing different HTTP verbs.
The default asp.net web api template gives some good hint on how to declare different verb methods e.g. -
public class UserController : ApiController
{
// GET api/<controller>/1
public User Get(int id)
{
}
// POST api/<controller>
public void Post([FromBody]User user)
{
}
// PUT api/<controller>/1
public void Put(int id, [FromBody]User user)
{
}
// DELETE api/<controller>/1
public void Delete(int id)
{
}
}
There is some good information here and here on designing restful api.
If you want to call your emthod from Angular, you can just use $http.post() method:
$http.post('/someUrl', data, config).then(successCallback, errorCallback);
Here you can find more information.

Using RestEasy, optionally pass a parameter as JSON in the POST

I have the following REST endpoint:
#POST
#Path("/id/{id}/doSomething")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes({ MediaType.APPLICATION_JSON })
public Response doSomething(#PathParam("id") final String id, MyObject foo) {
// does some stuff; checks for a null foo and handles it
}
The MyObject class has a single String field called justification.
I'd like to be able to hit this endpoint with no content at all, or with JSON that maps to the MyObject class. I'd like either way to work. In other words, foo can be instantiated, or foo can be null; I have code to handle both cases.
The problem is that the JSON content appears to be required to this endpoint, not optional. So during testing, I'm having to send JSON to the endpoint, or I receive a 500 error. Even if that JSON is just {} (I can also send { justification: "blah blah" } and that works as well). But sending no content at all results in a failed call; never even hits the endpoint.
So, my question is, how can I set this endpoint up so that I can POST to it with no content at all, or with JSON in the body that maps to foo, and have either way work?
Ultimately, I just need a way for the user to be able to send a justification to this endpoint, but not have to. And because justifications can be long, I can't have it as a query param or a path param.
Thanks!
You are not going to be able to hit the endpoint with no content at all because your endpoint says #Consumes({MediaType.APPLCIATION_JSON}). Besides there has to be some content while you're trying to POST to a class while using a web service.
Like you said, even if it is a NULL or a {}, it doesn't matter as long as it has some content coming in.
Passing no content to the service works only when you're making a GET request.
For all other HTTP methods such as POST,PUT and DELETE, you will mandatorily HAVE to send some data.
As a solution to your problem, what you possibly could do is that - check if the content you have received is a NULL or a {} and do no processing at all for them.
If you still have a confusion in the answer, depending upon whether you're using SOAP or REST, this thread should help you.
How to express 'null' value from web service as real null or empty string instead of 'null' string
Hope this helps.
I was able to accomplish what I wanted by writing a second method annotated with the same REST path. This second method does not have an #Consumes statement, and does not have the second parameter in its method declaration. Looks like this:
#POST
#Path("id/{id}/doSomething")
#Produces({ MediaType.APPLICATION_JSON })
public Response doSomethingWithoutJustification(#PathParam("id") final String id) {
doSomething(id, null);
}
This new method maps to the same path, but does not expect JSON and doesn't expose a second parameter. So when I POST with nothing in the request body at all, it hits doSomethingWithoutJustification, and when I do provide JSON in the request body, it hits doSomething. Of course, if I provide anything other than valid JSON in the request body, I receive a 500 response from the service, as I'd expect.
I'd hoped to specify an optional parameter with a single method, but this solution works perfectly.

Laravel return json or view

I'm developing an API where if the user specifies the action with .json as a suffix (e.g. admin/users.json), they get the response in the return of json, otherwise they get a regular html View.
Some actions may not have a json response, in which case they would just return a html View.
Does anyone have advice on how this can be implemented cleanly? I was hoping it could be achieved via the routing.
I suggest you to create your application as an api.
Foreach page, you need two controllers. Each controller use a different route (in your case, one route ending by .json, and one without).
The json controller return data in json form. The "normal" controller call the corresponding json route, deserialize the json, then pass the resulting array to the view.
This way, you've got a standardized api (and maintained, because your own app use it) available, as well as a "normal" website.
More information:
Consuming my own Laravel API
Edit: Maybe it's doable with a filter, but I'm not sure about that and I don't have time to try it myself right now.
In Laravel 5.x, to implement both capabilities like sending data for AJAX or JSON request and otherwise returning view template for others, all you have to do is check $request->ajax() or $request->isJson().
public function controllerMethod(Request $request)
{
if ($request->ajax() || $request->isJson()) {
//Get data from your Model or whatever
return $data;
} else {
return view('myView.index');
}
}

HTTP Status 0 from AngularJS Get for JSON

I'm running a $http.get for a JSON and am getting a status of 0. I've downloaded the same JSON and the get works locally, and in Python using the requests library I can get the JSON no problem, but in AngularJS it's not working. What I don't understand is why angular isn't getting it but everything else is. Code snippet below.
function AgentListCtrl($scope, $http) {
$http.get('http://foo.bar/api/objects').success(function(data) {
$scope.objects = data;
}).error(function(data, status) {
$scope.status1 = status;
});
This provides the JSON and parses it when using a local file, but otherwise it fails and sets status1 to 0.
Just to make this clear since is not directly stated in the above answer (but in its comments) and, like me, some Angular newbies may be spending some time on this:
Angular's $resource will be able to execute a REST verb on another server, which in turn will respond correctly (with a status 200). Angular will nevertheless fail with a cryptical message, identifyiable by the status 0. It is further misleading since, in a browser's debugger, you may actually see the server's answer.
Angular will do an OPTIONS request on a cross-domain request (at least for the default query() method) unless specified on the contrary. Usually the server will not answer with the desired content (i.e. your representation). One simple way of doing this per request is specifying the method to be 'GET'.
$resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'});
The server answering your REST requests MUST include the headers specified by CORS [1] in order to allow Angular to consume properly the response. Essentially this means including the Access-Control-Allow-Origin header in your response, specifying the servers from where the request comes from, that are allowed. This value may be *.
Complementing this answer for anyone integrating AngularJS with spring-data-rest-webmvc:
the HATEOAS json formatted response will not be properly consumed by Angular, producing instead the error Expected response to contain an array but got an object. This is solved by adding the isArray: false parameter to the $resouce's configuration;
a very to-the-point example of configuring CORS for the spring-data-rest-webmvc scenario is presented at [2] (see the SimpleCORSFilter)
[1] https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS
[2] https://spring.io/guides/gs/rest-service-cors/
In your code, the status assignment only occurs when the error happens. You should be able to get the status when the call was made successfully like this:
success(function(data, status, headers, config) {
$scope.objects = data;
$scope.status1 = status;
}).error(function(data, status) {
$scope.status1 = status;
});
I was having a similar problem myself. A third party API that returns JSON just fine through every other means was failing with status 0 when called through Angular's $http.get() method.
In my case there wasn't any CORS problem. Instead, the URL I was using for the API was not quite right and the server was issuing a 301 response. Angular wasn't respecting the redirect.
Word to the wise.

REST/JSON/MVC Return Values

Not a great title but I'm looking more for some guidance, have searched quite a bit. I'm building a web app with an MVC framework (but I think this is a more generic question). I'm trying to make many views that do a lot of AJAX style calls and say I have a site with users and they can add folders and files to their profile page. So the URL maybe like:
/profile/{id}
I have a Profile controller that returns a view with various information. I'd like files and folders listed on the profile to be dynamic so I want to populate it through AJAX calls. I was thinking I would have a URL like
/listFolders/{userId}
and
/listFiles/{folderId}
Is it reasonable to have these URLs return a JSON object for these two URLs and not even provide an HTML view (since, for the browser, the view will just be the whole profile page)? Also, what should I return for errors, say if the user/folder doesn't exist or the current logged in user doesn't have access the data? Is it reasonable to just set 404 or 403 HTTP error codes or do they need to return some kind of HTML? What if there are multiple reasons for it to fail and I'd like to pass that along? Should I arbitrarily choose HTTP error codes or define integer return codes like 0, 1, 2, etc? Also, should the URL specify that they are JSON, like listFoldersJSON instead of listFolders?
I have used JSON in my previous projects. For errors, we return error codes.
We decided to do so because we were dealing with API clients. So we want to deal with error codes (REST is based on HTTP, so it was appropriate to return error codes).
Since you are writing your own application, you can pretty much choose how you want to send your errors to the view. You can create a error json object and in the view you have to check whether this object is not null.
pretty much a if-else in the view. Else you can return error codes and check for the code before rendering the JSON into whatever view you want to.
I would go with error codes, because that complies with the REST philosophy.
Generally speaking, I handle this situation by throwing a 500 internal server error with a status message. Most client libraries such as jQuery provide built in error handling with a failure callback like:
jQuery.ajax({
success:function(response){
//do some success stuff
},
error:function (xhr, ajaxOptions, thrownError){
//handle error
alert(xhr.responseText);
}
});
It's entirely feasible to return JSON objects as opposed to actual views.
As far as the url, you can use listFolders and listFiles without taking on the JSON. However, I recommend you use lower case urls for the sake of how the server is setup. For instance, I know on Apache that sometimes listFiles would be fine, but listfiles would lead to missing page exception.
With regards to errors: You could setup a header of sorts in your JSON response and use whatever system you'd like. For instance, you could do something like
status_code: 0 //where 0 means successful
status_detail:success!
Where, if the status_code is something other than 0, you'd check the status_detail and know to ignore everything else inside the response.
Also, what should I return for errors, say if the user/folder doesn't exist or the current logged in user doesn't have access the data?
These are basic HTTP Error codes:
401 : Unauthorized
404 : Not found
There's a whole slew of error messages in the HTTP spec:
HTTP Status Code Definitions
Also, should the URL specify that they are JSON, like listFoldersJSON instead of listFolders?
Generally, a good way to handle this is for the client to set the 'accepts' header to something like 'text/json' or 'text/xml' and for the server to parse it out and respond with the correct response. This way you can use the same URL but send back different views of the data (if you ever wanted)