I have a AngularJS webapplication with a Jersey Backend Application.
Now everything is working fine using ngResource to access REST resource out of AngularJS. The only problem is with the DELETE option.
I have the following code to delete a course using my ngResource:
Course.deleteCourse = function(course) {
course.$remove({
courseId:course.id
});
return course;
};
In the backend (Jersey) I have the following code:
#DELETE
#Path("{id}")
public final void remove(#PathParam("id") final String id) {
System.out.println("DELETE ID = " + id);
}
If I try to delete an item the following url is called from Angular:
DELETE http://localhost:8080/manager-api/courses/5
This is fine (after me). If I call this url from CURL, i get the ssystem.out from the Backend posted to the console.
In the client-app (AngularJS) i get the following exception on the browser console:
DELETE http://localhost:8080/manager-api/courses/5 415 (Unsupported Media Type)
Anyone an idea what the problem might be? POST + GET are working fine.
I have the following consume/produce annotations:
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
Thanks in advance for your help.
Greets
Marc
EDIT:
I have tried to replace the way of accessing the REST services out of AngularJS with $http.
Now my service looks as below:
MyApp.factory("Course", ["$http", function ($http) {
var courseBaseUrl = "/api/courses/";
return {
show: function show(courseId) {
return $http.get(courseBaseUrl + courseId);
},
list: function list() {
return $http.get(courseBaseUrl, {});
},
remove: function remove(courseId) {
return $http.delete(courseBaseUrl + courseId, {});
},
save: function save(course) {
return $http.post(courseBaseUrl, course, {});
}
};
}]);
The result is still the same. The application calls e.g
DELETE http://localhost:8080/manager-api/courses/1
and receives a
DELETE http://localhost:8080/manager-api/courses/1 415 (Unsupported Media Type)
If I call the same DELETE call on Curl, everything works fine.
Thanks for your help
Marc
I came across this as well, the problem is angular always sets the Content-Type header to xml on DELETE requests and jersey will chuck an error as you have specified that your api consumes/produces JSON with the annotations.
So to fix it (from the client side), set the content-type header, eg:
.config(function($httpProvider) {
/**
* make delete type json
*/
$httpProvider.defaults.headers["delete"] = {
'Content-Type': 'application/json;charset=utf-8'
};
})
However, for reasons I dont understand/dont know of, angular will strip away the content-type header from the request if it has no data. This would make sense if it wasn't for the fact that browsers (chrome at least) will always send a content-type... Anyway you will have to go to the trouble of finding this in the angular source:
// strip content-type if data is undefined
if (isUndefined(config.data)) {
delete reqHeaders['Content-Type'];
}
and get rid of it. I dont know of a way to do this without editing the source. Maybe someone with better JS know-how, erm, knows how.
Alternatively, from the server side, you can do as Rob has suggested and change the Jersey configuration to allow consuming MediaType.APPLICATION_XML
#Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public final void remove(#PathParam("id") final String id) {
System.out.println("DELETE ID = " + id);
}
I had same issue, Try returning new instance of Course object in your delete method.
#DELETE
#Path("{id}")
public final Course remove(#PathParam("id") final String id) {
System.out.println("DELETE ID = " + id);
return new Course();
}
Using angularjs $resource (instead of $http), without "payload" in the request, the content-type is setted as text/plain.
So IMHO it's better a server side support.
"Postel's Law states that you should be liberal in what you accept and conservative in what you send. -- source"
#DELETE
#Path("{id}")
#Consumes({ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN })
public void remove(#PathParam("id") Long id) { ...
Related
I was wondering if there were any good techniques in keeping your WebAPI controller routes in sync with the client side.
For instance, you have a WebAPI controller BooksController. On the client you could invoke a method by calling the endpoint:
$.get('books/1');
Then one day you decide to rename the controller, or add a RoutePrefix. This breaks the client side code, as the endpoint has changed.
I came across the library WebApiProxy, which looks interesting. Does anyone have a good approach to solving this problem? Is there a reason to use string literals on the client that I may be overlooking?
I created a blog bost on te subject. Take a look :)
http://blog.walden.dk/post/2017/02/02/export-all-your-asp-net-webapi-endpoints-to-json
Im working on a post consuming it in javascript.. Anyway, this code exports the endpoints runtime, and will work on refactorings and route changes. It exports uri parameters as well, they can be used to be parsed in javascript and replaced with values from the client.
The simplest way to achieve waht you want, is to use the built-in ApiExplorer in ASP.NET WEBAPI. It searches for all "ApiController" implementations, and reads the route-attribute metadata.
public class EndpointManager
{
public IEnumerable<ApiMethodModel> Export()
{
//Use the build-in apiexplorer to find webapi endpoints
IApiExplorer apiExplorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();
//exclude endpoints without the attribute
var apiMethods = apiExplorer.ApiDescriptions.Select(ad => new ApiMethodModel(ad)).ToList();
return apiMethods;
}
}
You can create an endpoint that returns that generated data.
[RoutePrefix("api/endpoint")]
public class EndpointApiController : ApiController {
[HttpGet]
[Route("all")]
public IEnumerable<ApiMethodModel> All()
{
var endpoints = new EndpointManager().Export();
return endpoints;
}
}
Now all the endpoints can be reached at "/api/endpoint/all"
Here is an sample I was talking about in my comment to your question:
function getUrl(uri) {
var bookRoute = /books(.*?)/i;
var otherRoute = /something(.*?)/i;
if(uri.match(bookRoute)) {
return uri.replace(bookRoute, "http://localhost/webapi/books$1")
}
if(uri.match(otherRoute)) {
return uri.replace(otherRoute, "http://mydomain/api/something$1")
}
return uri;
}
alert(getUrl("books/1"));
alert(getUrl("something/realy/different/1"));
All you need is to define the routes in the body of your function.
I'm using angularjs with wildfly v8.2. I have a problem when try to pass multiple params in services in angularjs using get rest services. I've got this error : "Bad arguments passed to org.jboss.resteasy.spi.metadata.ResourceMethod#7a3ab29a" ( null, java.lang.Integer 1 ). How do i pass multiple parameter using get in angularjs. Anyone..
Below is my codes:
services.js
authListing: function (id, page, succFn) {
return $http.get(services.path + 'student/listing', {params: {page: page, id: id}}).success(succFn).error(cmnSvc.errorHandler);
}
rest.java //endpoint
#Path("/listing")
#GET
public Response listing(#QueryParam("id") String id, #QueryParam("page") int page) {
//Some method here
}
You are making the call with Angular correctly. However, I suspect you are passing in either a null or undefined argument based on the error message:
"Bad arguments passed to
org.jboss.resteasy.spi.metadata.ResourceMethod#7a3ab29a" ( null,
java.lang.Integer 1 )
My guess is that your endpoint isn't meant to take a null value for id and that is why it is puking.
You should be able to figure this out pretty quickly by simply opening up the developer tools in your browser, and inspecting the XHR request made to the server.
If it looks something like this: student/listing?id=undefined&page=1 then you know you have a problem.
Disclaimer: I'm a WebApi/BackBone beginner, so the question might be a bit odd since there is a lot about these components I don't really know and/or understand.
It would be nice to have the possibility to issue just ONE sync() call to the server to synchronize everything. I mean, when I saw sync() method, at first I thought it's used like that, but as soon as I saw the "create", "update", "delete" params I realized it's not. But there is an underlying problem related to Backbones default implementation for DELETE.
I've learned that classic implementation of Backbone.js allows one deleted (destroyed) model at a time to be sync'ed to the server. Created/modified (POST/PUT operations) content is sent in the request body itself, so the JSON is filled with the data and deserialized by WebApi model binding on the server. It doesn't work like that for DELETE, since body is always empty and reference to the model is made by URL parameters in query string. So, I guess to achieve that functionality, the request for DELETE should be sent in body as well as for POST/PUT.
Is there a possibility to change all of this behavior AND make it work with WebApi? I googled for that stuff already, but can't find anything to point me to the right direction.
What I have until now is a Backbone model, collection and a view set up.
Backbone.sync("create", this.collection); is called by the view on button click.
On the server side there is a WebApi controller set up with scaffolded methods:
// GET
public IEnumerable<Ponuda> Get()
{
return _storageService.GetPonude().ToList();
}
// GET
public Ponuda Get(int id)
{
return (Ponuda)_storageService.GetPonuda(id);
}
// POST
public void Post([FromBody]IEnumerable<Ponuda> value)
{
_storageService.CreatePonude(value);
}
// PUT
public void Put([FromBody]IEnumerable<Ponuda> value)
{
_storageService.ModifyPonude(value);
}
// DELETE
public void Delete(IEnumerable<int> value)
{
_storageService.RemovePonude(value);
}
EDIT: I'm reading about Marionette.js and it seems to offer standard model/view related functionalities out of the box. However, I still can't see the possibility to save/sync e.g. the entire modified collection at once.
To sync all contents at once :
For POST and PUT Http methods, you can use Backbone.Sync API.
For DELETE, you can directly use the ajax API for deleting the content in the server and use Backbone Collection remove API to delete the content in the client side.
I have written skeleton code which demonstrates on how to achieve the functionality:
var PersonModel = Backbone.Model.extend({
url: '/demo',
defaults: {
"id": 0,
"name": "",
"age": 0
}
});
var PersonCollection = Backbone.Collection.extend({
url: '/demo',
model: PersonModel
});
var model1 = new PersonModel({"name": "John", "age": 30});
var model2 = new PersonModel({"name": "Joseph", "age": 30});
var collection = new PersonCollection();
// model will be added locally on client side. It will not sync to the server.
collection.add(model1);
collection.add(model2);
// POST. This will create both the models together using a single REST API request.
Backbone.sync('create', collection);
// PUT. This will update both the models together using a single REST API request.
Backbone.sync('update', collection);
// Extract the model ids to be deleted
var modelIds = [model1.get('id'), model2.get('id')];
$.ajax({
method: 'DELETE',
url: '/demo',
data: JSON.stringify(modelIds), // This will add ids to the request body
contentType: 'application/json',
success: function() {
// On successful deletion on server end, delete the models locally.
collection.remove([model1, model2]);
}
});
Regarding the WebApi, since I have not worked on it, will not be able to guide you. Having worked on Spring Rest API, I can tell you above functionality should work with the WebApi.
I am very new to jQuery and json. I am trying to get UK Bank Holiday data from the following API/link so that I can create a function which determines if a selected date is a working day.
https://www.gov.uk/bank-holidays.json
I had an error for No Transport which I fixed by putting in:
jQuery.support.cors = true;
I am now getting Access Denied. I have no idea how to gain access to this because as I say, I am still learning all this.
function IsWorkingDay(date) {
var day = new moment(value, "DD-MM-YYYY").day();
jQuery.support.cors = true;
var bankHolidays = $.getJSON("https://www.gov.uk/bank-holidays.json").done(function(data) {
alert(data);
})
.fail(function(a, b, c) {
alert(b + ',' + c);
return day != 0 && day != 6;
}
});
My question is in two phases:
How do I get access? (Main question)
How do I move on to access the data? I have downloaded the json onto my computer to look at, just how I am going to go about translating this to javascript is what I am struggling on.
If you are blocked by CORS, and the service doesn't support JSONP, the easiest way to solve it is to create a proxy service for the actual service. So if you create a service on your server (which is the same that is serving the javascript), you can call that service, which in turn will fetch the data from the external service. On the server side, there is no CORS to worry about.
I don't know what your backend is, but the steps are as follows:
Create a service on your side that is exposed with a URL (e.g. /myapp/workingday)
Call this service instead of the real service
Your proxy service will get the JSON data and return it to the javascript
Edit
I don't know MVC4, but I suspect it's some of the same concepts as Spring MVC, so here's a Java example:
#Controller
public class HolidaysController {
#RequestMapping("/workingday")
public void isworkingDay(#RequestParam("day") Date day, HttpServletResponse response) {
// Call external service and get JSON
String json = callExternalService(day);
response.setContentType("application/json");
response.getWriter().write(json);
}
}
And in your javascript:
function IsWorkingDay(date) {
var day = new moment(value, "DD-MM-YYYY").day();
var bankHolidays = $.getJSON("/workingday").done(function(data) {
// process data
});
}
For this to work you need to use jsonp as illustrated here
BUT
running the above code throw an invalid label exception which as explained here and as #NilsH suggests is due to the server blocking it
I've been playing around with SignalR for a few days and I have to say that it's an absolutely phenomenal library. I've managed to get a few things working with it and was astounded at the simplicity, but I recently ran into a small problem.
I'm unable to call a Hub method from JavaScript in the browser. The request returns a 500 error code, and when I look at the error page source, I see this:
[ArgumentNullException: Value cannot be null.
Parameter name: s]
System.IO.StringReader..ctor(String s) +10207225
Newtonsoft.Json.Linq.JObject.Parse(String json) +74
SignalR.Hubs.HubRequestParser.Parse(String data) +78
SignalR.Hubs.HubDispatcher.OnReceivedAsync(IRequest request, String connectionId, String data) +266
SignalR.<>c__DisplayClass6.<ProcessRequestAsync>b__4(String data) +84
SignalR.Transports.ForeverTransport.ProcessSendRequest() +159
SignalR.Transports.ForeverTransport.ProcessRequestCore(ITransportConnection connection) +149
SignalR.Transports.ForeverTransport.ProcessRequest(ITransportConnection connection) +42
SignalR.PersistentConnection.ProcessRequestAsync(HostContext context) +1087
SignalR.Hubs.HubDispatcher.ProcessRequestAsync(HostContext context) +251
SignalR.Hosting.AspNet.AspNetHandler.ProcessRequestAsync(HttpContextBase context) +656
SignalR.Hosting.AspNet.HttpTaskAsyncHandler.System.Web.IHttpAsyncHandler.BeginProcessReques t(HttpContext context, AsyncCallback cb, Object extraData) +143
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9479007
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +178
My server-side code is:
public class SourceHub : Hub
{
public void RegisterSource(string source)
{
Groups.Add(Context.ConnectionId, source);
}
}
and on the client-side I have:
var SourceHub = $.connection.sourceHub;
$.connection.hub.start().done(function () {
SourceHub.registerSource("test");
});
I've been digging for a while, but I just can't find the source of the problem... could someone please help me out?
Preface: I'm on edobry's team for this project, and I'm the backend guy, so this foray into frontend was new and fascinating to me.
So, after some careful dissection of the code as I stepped through the project, it turns out that the POST data in the request variable was coming up empty. The data was getting sent properly up to the $.ajax call, but never arriving at the server. Some more hunting and I had uncovered something suspicious that edobry and I were then able to discern what was happening.
We're using Ajax for other parts of the page, and the ajax method is called several times, so we had factored out some settings we set on every method call into $.ajaxSetup. One of these was dataType getting set to "text", and contentType to "application/json". It seems that signalR is not overriding global ajax settings properly. I'm not sure if this is a bug or intended behavior, but it should be documented better if that's the case (and if it is, feel free to call us dumb. :))
Your method is called RegisterSource and you call registerSources instead of registerSource in javascript.