I'm using AngularJS to retrieve a list of objects from a Server. The server side is being made with Spring MVC.
So, I'd like to return a list of these objects as JSON format, but, I'd like to return an informative message object if the server coudn't retrieve the data for any reason.
Can I do it without encapsulating the List<> and the Message objects in a DTO (Data Transfer Object)?
How can I check on AngularJS the received JSON if it can be from 2 different types?
Here is a fragment of my controller class. It currently returns a list of the MyType I'd like to return. But the idea is to convert it to a generic type (Object, maybe?) and return from this request the list or the message, depending on the case.
#RequestMapping(value = RestPaths.LIST_MYTYPE_BY_OWNER, method = RequestMethod.GET)
public #ResponseBody List<MyType> listByOwner(#RequestBody Owner o) {
List<MyType> myType = myService.list(o);
return myType;
}
This is the service that consults the controller. How could I treat the data, considering that the JSON could have different formats?
this.listMyType = function(ownerId){
var deferred = $q.defer();
$http.get('/rest/my/list_by_owner',{})
.then(function (response) {
if (response.status == 200) {
deferred.resolve(response.data);
}
else {
deferred.reject('Error retrieving list of myType');
}
});
return deferred.promise;
}
I wouldn't use an exception for flow control. If both cases (list is empty or it is not) are valid, handle both of them in your handler method. Use ResponseEntity to encapsulate your response. Return a String message in one case (empty) and a List<MyType> in the other (not empty).
#RequestMapping(value = RestPaths.LIST_MYTYPE_BY_OWNER, method = RequestMethod.GET)
public ResponseEntity<?> listByOwner(#RequestBody Owner o) {
List<MyType> myType = myService.list(o);
if (myType.isEmpty()) {
return new ResponseEntity<>("The message", HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(myType, HttpStatus.OK);
}
All handler method return values are handled by HandlerMethodReturnValueHandler objects. The corresponding one for ResponseEntity is HttpEntityMethodProcessor. It handles the ResponseEntity's body the same way #ReponseBody would.
So a String would be written directly to the response as text/plain (or html) and the List<MyType> would be serialized to application/json by default (or whatever you have configured).
You can add response headers to the ResponseEntity which can also help determine how the content will be serialized (content-type, etc.).
My suggestion is to throw an exception, then add in a spring exception handler which sets the appropriate status code and message:
#RequestMapping(value = RestPaths.LIST_MYTYPE_BY_OWNER, method = RequestMethod.GET)
public #ResponseBody List<MyType> listByOwner(#RequestBody Owner o) {
List<MyType> myType = myService.list(o);
if (myType.size() == 0) {
throw new ResourceNotFoundException("List was empty");
}
return myType;
}
and
#ControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(ResourceNotFoundException.class)
public void handleException(Exception e, HttpServletReponse response) {
response.setStatus(HttpStatus.NOT_FOUND);
response.getOutputStream().write(e.message);
}
}
Here is a pretty good write up on spring mvc exception handling: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
Related
I'm trying to call a .NET Core API from AngularJS. In the AngularJS I'm calling the method like this:
$http({
method: 'POST',
url: '/api/message/transaction/' + this.transaction.id,
data: { "transactionJson": "hello"}
})
.then(function (response) {
var r = response;
})
My .NET Core API method is like this:
[Route("~/api/message/transaction/{transactionId}")]
[HttpPost]
public async Task<ActionResult<DeviceEventsTransactionmsg>> PostTransaction([FromBody] string transactionJson)
{
I'm getting a 400 Bad Request response back from the server. How do I fix it?
I realised the type for the parameter must be a type that has a property named TransactionJson, so I need to define a new C# type:
public class TransactionData() {
public string TransactionJson
}
Then in the API method:
[Route("~/api/message/transaction/{transactionId}")]
[HttpPost]
public async Task<ActionResult<DeviceEventsTransactionmsg>> PostTransaction([FromBody] TransactionData transactionJson)
{
getting a 400 Bad Request response back from the server. How do I fix it?
To fix the issue, as your mentioned, one solution is modifying action parameter, like below.
public async Task<ActionResult<DeviceEventsTransactionmsg>> PostTransaction([FromBody] TransactionData transactionJson)
{
//...
//code logic here
TransactionData class
public class TransactionData
{
public string TransactionJson { get; set; }
}
Besides, we can also implement and use a custom plain text input formatter to make PostTransaction action method that accepts a string-type ACTION parameter work well.
public class TextPlainInputFormatter : TextInputFormatter
{
public TextPlainInputFormatter()
{
SupportedMediaTypes.Add("text/plain");
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian);
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
string data = null;
using (var streamReader = new StreamReader(context.HttpContext.Request.Body))
{
data = await streamReader.ReadToEndAsync();
}
return InputFormatterResult.Success(data);
}
}
Add custom formatter support
services.AddControllers(opt => opt.InputFormatters.Insert(0, new TextPlainInputFormatter()));
Test Result
Im migrating a JAX-RS application to Quarkus using the resteasy-reactive-jackson extension. One of the resource methods should return an Excel document if the Accept-header is application/vnd.ms-excel. The Excel document is created in a MessageBodyWriter<>. This works as expected in the old application (KumuluzEE, Jersey).
My requests are successfully routed to the resource method, the Accept-header is present but when the response entity arrives at my implementation of isWriteable in the MessageBodyWriter<> the mediaType parameter is always application/json. I have tried implementing a ServerMessageBodyWriter<> but that did not make any difference.
Any ideas of whats going on?
Im using Quarkus v2.2.
Edit 2:
The service interface is in it's own Maven module:
#Path("")
#Produces(MediaType.APPLICATION_JSON)
#RegisterRestClient
#RegisterClientHeaders
public interface MyResource {
#GET
#Path("{id}")
// #Produces({"application/vnd.ms-excel", MediaType.APPLICATION_JSON}) // Works
#Produces({MediaType.APPLICATION_JSON, "application/vnd.ms-excel"}) // Does not work
Response getData(#PathParam("id") Long id);
}
The resource implementation and MessageBodyWriter:
public class MyResourceImpl implements MyResource {
#Context
HttpHeaders httpHeaders; // getAcceptableMediaTypes() returns mediatypes
// matching Accept-header as expected
#Override
public Response getData(#PathParam("id") Long id) {
return Response.ok().entity(new MyData()).build();
}
}
#Provider
#Produces({"application/vnd.ms-excel"})
public class ExcelMessageBodyWriter implements MessageBodyWriter<MyData> {
#Override
public boolean isWriteable(Class<?> aClass, Type type,
Annotation[] annotations, MediaType mediaType) {
// mediaType is always MediaType.APPLICATION_JSON_TYPE when JSON
// is listed first in #Produces in service interface
return aClass == MyData.class && mediaType.getType().equals("application")
&& mediaType.getSubtype().equals("vnd.ms-excel");
}
...
}
Changing #Produces({MediaType.APPLICATION_JSON, "application/vnd.ms-excel"}) on the resource method to #Produces({"application/vnd.ms-excel", MediaType.APPLICATION_JSON}) solved my problem. This can't be the expected behaviour?
I have this AngularJS Http Call
$http({
method: "POST",
url: Helper.ApiUrl() + '/Api/Case/SendCase',
data: { obecttype1, obj2, obj3},
}).then(function mySuccess(response) {});
Ant this ASP.net Web Api method
[HttpPost]
[Route("Api/Path/SendCase")]
public int SendCase(object application)
{
string applicantName = ((Newtonsoft.Json.Linq.JObject)application)["applicant"].ToString();
obecttype1 obj = JsonConvert.DeserializeObject<obecttype1>(((Newtonsoft.Json.Linq.JObject)application)["obecttype1"].ToString());
.........................
return ID;
}
This works pretty well, but I feel it is a bit dirty because I am parsing my objects in my method, so my question is
Is the are way to send multiple objects as params in a POST method, I would prefer to avoid modifying my model, avoid creating a class for this
So my Api Method would look like this
public int SendCase(class1 obecttype1, class2 obj2, class3 obj3)
"Is the are way to send multiple objects as params in a POST method, I would prefer to avoid modifying my model, avoid creating a class for this"
By design HTTP Post can only have one body and web api will try to cast the body to the parameter defined in the method signature. So sending multiple objects in the body and trying to match these against multiple params in the method signature will not work. For that you need to define a class which holds the other classes and match the body signature.
public class postDTO
{
public class1 class1Data { get; set; }
public class2 class2Data { get; set; }
public class3 class3Data { get; set; }
}
//The api signature
public int SendCase(postDTO application)
If you still don't want to add the new class then I would use the JObject directly as the parameter as this
[HttpPost]
public int SendCase(JObject jsonData)
{
dynamic json = jsonData;
JObject class1DataJson = json.class1Data;
JObject class2DataJson = json.class2Data;
JObject class3DataJson = json.class3Data;
var class1Data = class1DataJson.ToObject<class1>();
var class2Data = class2DataJson.ToObject<class2>();
var class3Data = class3DataJson.ToObject<class3>();
}
1. Define models for the parameters
public class ClassType1
{
public int Num1 { get; set; }
public string Str1 { get; set; }
}
public class ClassType2
{
public double Test2 { get; set; }
}
2. Use the models as the parameters on the API controller method
// Sorry this example is setup on .Net Core 2.0 but I think the previous
// versions of Web Api would have similar/same behavior
[Route("api/[controller]")]
public class ValuesController : Controller
{
[HttpPost]
public void Post(ClassType1 ct1, ClassType2 ct2)
{}
}
3. When posting, your objects inside the data {} have to have the keys that match the parameter name you defined on the Controller method
jQuery ajax
$.ajax({
method: 'post',
url: 'http://localhost:53101/api/values',
dataType: 'json',
data: {
// It takes key value pairs
ct1: {
num1: 1,
str1: 'some random string'
},
ct2: {
test2: 0.34
}
}
});
To summarize, yes you can post multiple objects back to the server, as long as
You define a key for each object and the key has to match the parameter name you define on the server method.
The object structure has to match.
-- update --
Just as a proof, here is the screenshot:
We have an app that uses DefaultHttpBatchHandler to accept multi-part POST requests. I believe it to be a bit clunky for many reasons but it is the built-in way to accept multiple objects on a single request in a structured fashion.
https://msdn.microsoft.com/en-us/library/system.web.http.batch.defaulthttpbatchhandler(v=vs.118).aspx
As for the script to create something, that I don't know about. Our callers that use this API are C# services that can create the multi-part requests using a simple client library we provide to help them do just that.
I am working on a project using angularjs+springboot. Am trying to send email via my application using spring-boot-starter-mail. The message and object of the email are written by the user in a form. what I want to do is to get the message and object values in my RestController using #RequestBody.
the function in my service.js
// send mail
var sendMail = function(id, objet, msg) {
var deferred = $q.defer();
$http.post(urlBase + id, objet, msg).then(
function(response) {
deferred.resolve(response.data);
}, function(errResponse) {
console.error('Error while sending email');
deferred.reject(errResponse);
});
return deferred.promise;
}
the method in my restContoller
#RestController
public class EmailController {
#Autowired
private JavaMailSender javaMailSender;
#Autowired
UtilisateurService service;
#RequestMapping(value = "/users/{id}", method = RequestMethod.POST)
public ResponseEntity<Void> sendMail(#PathVariable("id") int id, #RequestBody String objet,
#RequestBody String msg) {
Utilisateur currentUser = service.findById(id);
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(currentUser.getEmailUtil());
message.setSubject(objet);
message.setText(msg);
javaMailSender.send(message);
return new ResponseEntity<Void>(HttpStatus.OK);
}}
This throws this exception :
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<java.lang.Void> com.sla.utilisateur.controller.EmailController.sendMail(int,java.lang.String,java.lang.String)
How can I fix it?
thank you,
Your usage of $http.post is not correct. You should have a look at the AngularJS POST documentation. $http.post arguments are the following:
post(url, data, [config]);
AngularJS sends the data by default in JSON. So you should send the request using the following statement (for example):
$http.post(urlBase + id, {subject:objet, body:msg})
And in your controller you should define only one #RequestBody maps for the ease of the example to a Map (You could change it to a POJO. ):
#RequestMapping(value = "/users/{id}", method = RequestMethod.POST)
public ResponseEntity<Void> sendMail(#PathVariable("id") int id, #RequestBody Map<String,String> msg) {
Utilisateur currentUser = service.findById(id);
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(currentUser.getEmailUtil());
message.setSubject(msg.get("subject");
message.setText(msg.get("body"));
javaMailSender.send(message);
return new ResponseEntity<Void>(HttpStatus.OK);
}}
I'm trying to test that the model returned from my Nancy application is as expected. I have followed the docs here but whenever I call the GetModel<T> extension method it throws a KeyNotFoundException.
System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
I know what the error means but I'm failing to see why it's being thrown.
Here's my module
public class SanityModule : NancyModule
{
public SanityModule()
{
Get["sanity-check"] = _ => Negotiate.WithModel(new SanityViewModel { Id = 1 })
.WithStatusCode(HttpStatusCode.OK);
}
}
my view model
public class SanityViewModel
{
public int Id { get; set; }
}
and here's my test
[TestFixture]
public class SanityModuleTests
{
[Test]
public void Sanity_Check()
{
// Arrange
var browser = new Browser(with =>
{
with.Module<SanityModule>();
with.ViewFactory<TestingViewFactory>();
});
// Act
var result = browser.Get("/sanity-check", with =>
{
with.HttpRequest();
with.Header("accept", "application/json");
});
var model = result.GetModel<SanityViewModel>();
// Asset
model.Id.ShouldBeEquivalentTo(1);
}
}
Debugging this test shows that the module is hit and completes just fine. Running the application shows that the response is as expected.
Can anyone shed some light on this?
Thanks to the lovely guys, albertjan and the.fringe.ninja, in the Nancy Jabbr room we've got an explanation as to what's going on here.
TL;DR It makes sense for this to not work but the error message should be more descriptive. There is a workaround below.
The issue here is that I am requesting the response as application/json whilst using TestingViewFactory.
Let's take a look at the implementation of GetModel<T>();
public static TType GetModel<TType>(this BrowserResponse response)
{
return (TType)response.Context.Items[TestingViewContextKeys.VIEWMODEL];
}
This is simply grabbing the view model from the NancyContext and casting it to your type. This is where the error is thrown, as there is no view model in NancyContext. This is because the view model is added to NancyContext in the RenderView method of TestingViewFactory.
public Response RenderView(string viewName, dynamic model, ViewLocationContext viewLocationContext)
{
// Intercept and store interesting stuff
viewLocationContext.Context.Items[TestingViewContextKeys.VIEWMODEL] = model;
viewLocationContext.Context.Items[TestingViewContextKeys.VIEWNAME] = viewName;
viewLocationContext.Context.Items[TestingViewContextKeys.MODULENAME] = viewLocationContext.ModuleName;
viewLocationContext.Context.Items[TestingViewContextKeys.MODULEPATH] = viewLocationContext.ModulePath;
return this.decoratedViewFactory.RenderView(viewName, model, viewLocationContext);
}
My test is requesting json so RenderView will not be called. This means you can only use GetModel<T> if you use a html request.
Workaround
My application is an api so I do not have any views so changing the line
with.Header("accept", "application/json");
to
with.Header("accept", "text/html");
will throw a ViewNotFoundException. To avoid this I need to implement my own IViewFactory. (this comes from the.fringe.ninja)
public class TestViewFactory : IViewFactory
{
#region IViewFactory Members
public Nancy.Response RenderView(string viewName, dynamic model, ViewLocationContext viewLocationContext)
{
viewLocationContext.Context.Items[Fixtures.SystemUnderTest.ViewModelKey] = model;
return new HtmlResponse();
}
#endregion
}
Then it is simply a case of updating
with.ViewFactory<TestingViewFactory>();
to
with.ViewFactory<TestViewFactory>();
Now GetModel<T> should work without needing a view.