ResponseEntity Spring MVC - rest

What is the best way to return an error message with ReponseEntity?
Say I have the following method
#Transactional
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<User> getUser(#PathVariable("id") Long id) {
User user = userRepository.findOne(id);
if (user == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
else {
return new ResponseEntity<>(user, HttpStatus.OK);
}
Now what if I want to return an error message to the front end? I can't do the following because the method return type is
ResponseEntity<User>
not
ResponseEntity<String>
so this will not work
if (user == null) {
return new ResponseEntity<>("User does not exist", HttpStatus.NOT_FOUND);
}
I could make the method return type
ResponseEntity<Object>
but that just seems slopy and bad practice. I want to be able to return at least a brief error message that gives some indication of what went wrong to the front end. How is the best way to go about doing this?
Update:
After some digging around I came up with this and it seems to work but curious if it would have a performance impact.
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<?> getUser(#PathVariable("id") Long id) {
User user = userRepository.findOne(id);
if (user == null) {
return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND);
}
else {
return new ResponseEntity<>(user, HttpStatus.OK);
}
}

I realise you asked specifically about returning the error message using ReponseEntity, but you could also consider using Spring MVCs exception handling to acheive the same outcome:
// Example from the linked Spring article:
#RequestMapping(value="/orders/{id}", method=GET)
public String showOrder(#PathVariable("id") long id, Model model) {
Order order = orderRepository.findOrderById(id);
if (order == null) throw new OrderNotFoundException(id);
model.addAttribute(order);
return "orderDetail";
}
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
public class OrderNotFoundException extends RuntimeException {
// ...
}

Related

ResponseEntity in RestController

i need your help.
I have method for creating entity in RestController, but i can not understand how to use ResponseEntity. I just return ResponseEntity.ok, but it's not correct i think.
I got advice to wrap it into object with data and success fields, but i didn't get it.
#PostMapping()
public ResponseEntity<String> create(#Valid #RequestBody Course course) {
try {
courseService.add(course);
} catch (ServiceException e) {
log.error("Can not create course", e);
}
return ResponseEntity.ok("Course is valid");
}
#PutMapping("/{id}")
public ResponseEntity<String> update(#Valid #RequestBody Course course, #PathVariable("id") int id)
throws ServiceException {
courseService.update(id, course);
return ResponseEntity.ok("Course is valid");
}
You can try this: ResponseEntity<>("Course is valid", HttpStatus.OK)
For more details and guide : https://www.baeldung.com/spring-response-entity
I did something like this:
return new ResponseEntity<>(course, HttpStatus.CREATED);

Spring boot return multiple response code with message body in a POST request

In my spring boot app, I want to return different types of response codes with response body.
#RequestMapping(method = RequestMethod.POST, value = "/users")
public ResponseEntity<User> userSignsUp(#RequestBody User user) {
if(userService.getUserByNic(user.getNic()).equals(userService.getUserByNic(user.getNic()))) {
UserErrorBean userError = new UserErrorBean("User already exist","406 error");
return ResponseEntity<>(userError ,HttpStatus.CONFLICT); }
userService.userSave(user);
return ResponseEntity<>(user, HttpStatus.CREATED);
}
This is my rest Controller and I want to return different responses based on different conditions. But it only returns condition if user NIC condition is met. If add user to database, it throws NullPointerException().
I want to return responses according to the request.
You can write a simple RestController like this.
#PostMapping("/users")
public ResponseEntity<User> userSignsUp(#RequestBody User user) {
if(user == null){
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
if(userService.getUserByNic(user.getNic()).equals(userService.getUserByNic(user.getNic()))) {
UserErrorBean userError = new UserErrorBean("User already exist","406 error");
return new ResponseEntity<>(userError, HttpStatus.CONFLICT);
}
if(userService.userSave(user)) { // make it return boolean on success or failed
return new ResponseEntity<>(user, HttpStatus.CREATED);
} else {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}

What should my repository return on a http post when the posted id (foreign key) is wrong

Given the user sends a valid token to an api endpoint via fiddler/postman, he could post a resource (pupil) for a related resource (schoolclass).
When the schoolclass id
does not exist yet in the database
does exist already in the database but this schoolclass Id belongs to another user.
does exist in the database and belongs to the passed userId
Then
What would you change in the Controller and Repository class to make it work for all 3 cases using a REST api + repository pattern.
Controller:
[HttpPost("~/api/pupils")]
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
return Ok(pupil.Id);
}
Repository:
public async Task CreatePupil(Pupil pupil, int schoolclassCodeId, string userId)
{
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode != null)
{
schoolclassCode.Pupils.Add(pupil);
await context.SaveChangesAsync();
}
}
NOTE
At the moment the last of the 3 use cases is implemented!
From REST prospective you need to return 400 or 404 depending on your design.
If your route need to be like /classes/{id}/users/{id}/pupil I thing you need to use 404 in case user or class is wrong.
In case of separate route (as I can see in your question) I think this should be 400 code as request URL is pointing to valid resource but payload is invalid.
In both cases I think the batter error handling strategy here is to write some set of custom exceptions (like EntityNotFondException, EntityInvalidException, BusinessLogicException) and throw them from repository in case something is wrong. Then you can create some global action filter or OWIN middleware to catch those exceptions and translate them to correct response status codes with appropriate messages
Example:
public class NotFoundException : Exception
{
public NotFoundException(Type entityType)
: base($"Entity {entityType.Name} was not found")
{
}
}
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
public ApiExceptionFilterAttribute()
{
}
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var exception = actionExecutedContext.Exception;
if (exception == null)
return;
if (exception is HttpResponseException)
return;
var entityNotFoundException = exception as NotFoundException;
if (entityNotFoundException != null)
{
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, entityNotFoundException.Message);
return;
}
}
}
Usage:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if(schoolclassCode == null)
throw new NotFoundException(typeof(Schoolclass));
You can throw validation exceptions in the same way. E.g:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId);
if(schoolclassCode == null)
throw new InvalidModelStateException("Schoolclass was not found.")
if(schoolclassCode.UserId != userId)
throw new InvalidModelStateException("Schoolclass is owned by different user.")
... etc.
I always use Result classes for returning state from a service class (wouldn't implement that in Repository as it shouldn't contain business logic):
public class QueryResult
{
private static readonly QueryResult success = new QueryResult { Succeeded = true };
private readonly List<QueryError> errors = new List<QueryError>();
public static QueryResult Success { get { return success; } }
public bool Succeeded { get; protected set; }
public IEnumerable<QueryError> Errors { get { return errors; } }
public static QueryResult Failed(params QueryError[] errors)
{
var result = new QueryResult { Succeeded = false };
if (errors != null)
{
result.errors.AddRange(errors);
}
return result;
}
}
public class QueryResult<T> : QueryResult where T : class
{
public T Result { get; protected set; }
public static QueryResult<T> Suceeded(T result)
{
if (result == null)
throw new ArgumentNullException(nameof(result));
var queryResult = new QueryResult<T>
{
Succeeded = true,
Result = result
};
return queryResult;
}
}
public class QueryError
{
public string ErrorId { get; set; }
public string ErrorMessage { get; set; }
}
And use it like
var schoolclassCode = await context.Schoolclasses
.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode == null)
return QueryResult.Failed(new QueryError
{
ErrorId = 1,
ErrorMessage = "Invalid User Id"
});
Edit:
Just as an addition and rule of thumb
Services which operate on one or multiple entities and perform user input validation should return Result classes
Domain Models (which you don't seem to use, since you use a repository and Repository + Rich Domains doesn't work out well in real life applications) should throw exception (i.e. InvalidOperationException or ArgumentException, ArgumentNullException). Doing Result-types her will pollute the model and mix the separation of responsibility (Domain Model will suddenly also do validation instead only guarding against invalid state)
Using XxxResult type classes gives you an easy way to transport one or multiple errors back to the user, where an exception should act as an guard against your domain model getting into invalid state.
Edit 2
In response to the comments:
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
var result = await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok(pupil.Id);
}
Edit 3
Example with 3 parameters for let's say /api/schoolclasses/1/students/2/lessons/2 (Update an existing lesson to the student with the id 2 for the school class with id 1).
// on SchoolClasses Controller
[HttpPost("{schoolClassId:int}/students/{studentId:int}/lessons/{lessonId:int}")]
public async Task<IActionResult> Post([FromBody]Lessons lessonDto)
{
// rough input validation, do first to avoid db hits
if(!ModelState.IsValid)
return BadRequest(ModelState);
// best put logic into service classes i.e. SchoolClassService
var result = schoolClassService.UpdateLessonFor(schoolClassId, studentId, lessonDto)
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok();
}
Content of UpdateLessonsFor
List<ErrorMessage> errors = new List<ErrorMessage>();
// with .Include to include both student and all of his lessons
// does student exist?
// Hits db once and gets both, student and all lessons in a single query
var student = _context.SchoolClasses
.Include(sc => sc.Students)
.ThenInclude(s => s.Lessons)
.Where(sc => sc.SchoolClassId == schoolClassId)
.SelectMany(sc => sc.Students)
FirstOrDefault(s => s.StudentId == studentId);
if(student==null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 1, ErrorMessage = "Student or School Class not found" } );
// Doesn't hit the database, since lessons have been loaded with the above call
var lesson = student.Lessons.Any(l => l.LessonId = lessonId))
if(lesson == null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 2, ErrorMessage = "Lesson not found. " } );
// modify it
lesson.SomeValue = dto.SomeValue;
try
{
} catch(Exception ex) {
return QueryResult.Failed(new ErrorMessage { ErrorId = 3, ErrorMessage = "Couldn't update the lesson. Try again and if the error appears again, contact the administrator." } );
} finally {
return QueryResult.Suceeded;
// or if you also want to return a result
return QueryResult.Suceeded(lesson);
}
Also from the comments of the other answer: Don't put logic into your repository, that's what services are for when you use anemic domain (models have no logic, all in services) or have thin service layer and put most logic into domain service. But that's out of the scope.

#ModelAttribute for Rest PUT - request param null

I need to populate my pojo class based on the request param 'type'.
so I have code like
#ModelAttribute
public void getModelObject(HttpServletRequest request, ModelMap modelMap) {
String typeCombo = request.getParameter("type");
System.out.println("typeCombo: " + typeCombo);
if (typeCombo != null) {
if (condition) {
modelMap.addAttribute("modelObj", new ClassB()); //ClassB extends ClassA
} else if (another condition) {
modelMap.addAttribute("modelObj", new ClassC()); //ClassC extends ClassA
} else {
System.out.println("no type found");
}
} else {
System.out.println("typecombo null");
}
}
I use above method to get create correct subclasses which will be used to add / update. The above one works fine in case of "POST" - for creating a record. But for "PUT" request.getParameter("type") always returns null. So for editing, I'm not able to get correct subclasses.
Below are my post and put request mapping:
#RequestMapping(value = "", method = RequestMethod.POST, headers = "Accept=*/*")
#ResponseBody
public String addCredentials(#ModelAttribute("modelObj") Credential credential,
ModelMap modelMap) {
//code
}
#RequestMapping(value = "/edit/{id}", method = RequestMethod.PUT, headers = "Accept=*/*")
#ResponseBody
public Credential editCredential(#ModelAttribute ("modelObj") Credential credential, #PathVariable long id, ModelMap model) {
//code
}
Any help is much appreciated.
Register the filter HttpPutFormContentFilter like this:
<beans:bean id="httpPutFormContentFilter"
class="org.springframework.web.filter.HttpPutFormContentFilter" />

MVC 2.0 - Custom handling of all errors to return json

I have an MVC 2 app that I want all requests to return json. I have overridden a HandleErrorAttribute and an AuthorizeAttribute. My goal is that all errors (even 403 and 404) are returned as json.
Here is my error handler. ExceptionModel is a simple class defining any error returned by my application. The Exception handler is a class that translates the error details into a formatted e-mail and sends it to me.
public class HandleErrorJsonAttribute : System.Web.Mvc.HandleErrorAttribute
{
public override void OnException(ExceptionContext context)
{
context.ExceptionHandled = true;
RaiseErrorSignal(context.Exception);
context.RequestContext.HttpContext.Response.ContentType = "application/json";
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(context.HttpContext.Response.Output, new ExceptionModel(context.Exception));
}
private static void RaiseErrorSignal(Exception ex)
{
IExceptionHandler handler = Resolve();
handler.HandleError(ex.GetBaseException());
}
private static IExceptionHandler Resolve()
{
return ServiceLocator.Locate<IExceptionHandler>();
}
}
Here is the Exception model for clarification
public class ExceptionModel
{
public int ErrorCode { get; set; }
public string Message { get; set; }
public ExceptionModel() : this(null)
{
}
public ExceptionModel(Exception exception)
{
ErrorCode = 500;
Message = "An unknown error ocurred";
if (exception != null)
{
if (exception is HttpException)
ErrorCode = ((HttpException)exception).GetHttpCode();
Message = exception.Message;
}
}
public ExceptionModel(int errorCode, string message)
{
ErrorCode = errorCode;
Message = message;
}
}
and finally, my custom authorize attribute. I an using forms auth, but I did not want any of the automatic redirection. I simply want the error to show on the screen and stop any further processing.
public class AuthorizeTokenAttribute : System.Web.Mvc.AuthorizeAttribute
{
public bool SuperAdminOnly { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool authorized = base.AuthorizeCore(httpContext);
if(!SuperAdminOnly)
return authorized;
if(!authorized)
return authorized;
return SessionHelper.UserIsSuperAdmin(httpContext.User.Identity.Name);
}
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
throw new HttpException(403, "Access Denied");
}
}
This all works great for most errors, but it is missing one thing. I have a controller action like this.
[AuthorizeToken]
[HttpPost]
public JsonResult MyAction()
{
return new JsonResult();
}
It works fine when you submit via post, but on a get I receive an unhandled 404 error.
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource
you are looking for (or one of its
dependencies) could have been removed,
had its name changed, or is
temporarily unavailable. Please
review the following URL and make sure
that it is spelled correctly.
Requested URL: /MyController/MyAction
Version Information: Microsoft .NET
Framework Version:4.0.30319; ASP.NET
Version:4.0.30319.1
This happens on a GET, which is to be expected as default behavior. However, how can I handle for this condition so that I could instead return json like this
{"ErrorCode":404,"Message":"Page Not Found"}
To handle errors personally I prefer the Application_Error event in Global.asax:
protected void Application_Error(object sender, EventArgs e)
{
var exception = Server.GetLastError();
Response.Clear();
Server.ClearError();
var httpException = exception as HttpException;
var routeData = new RouteData();
routeData.Values["controller"] = "Errors";
routeData.Values["action"] = "Index";
routeData.Values["error"] = exception;
IController errorController = new ErrorsController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
and then have an ErrorsController:
public class ErrorsController : Controller
{
public ActionResult Index(Exception exception)
{
var errorCode = 500;
var httpException = exception as HttpException;
if (httpException != null)
{
errorCode = httpException.ErrorCode;
}
return Json(new
{
ErrorCode = errorCode,
Message = exception.Message
}, JsonRequestBehavior.AllowGet);
}
}