This is very similar, but my question is different: Return content with IHttpActionResult for non-OK response
Considering the question is different, I'm asking for a more concise answer, if it exists.
My architecture is the following:
Javascript/jQuery call to a backend controller
Backend controller calls a WebAPI service
WebAPI service queries db (etc) and returns data
I have the following simplified code (Web API)...
Example 1 return error if product id doesn't exist:
public IHttpActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
Example 2 return empty data if product id doesn't exist:
public IHttpActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
return Ok(product);
}
Client side JS:
$.getJSON("example.json", function() {
alert("success");
})
.done(function() { alert('Product retrieved'); })
.fail(function() { alert('Product doesn't exist. '); })
.always(function() { ... });
I've read many many times that using exceptions to control flow is bad practice, which is in effect what will happen if I use NotFound() since it will hit the .fail function, suggesting there was an error (which there wasn't).
In another case when the comment has to be approved by someone other than the person who inserted the comment:
public IHttpActionResult ApproveComment(int rowId, string userName)
{
try {
return Ok(BusinessLogicLayer.ApproveComment(rowId, userName));
}
catch(Exception ex)
{
// elmah.logerr...
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.InnerException == null ? ex.Message : ex.InnerException.Message));
}
}
BusinessLogicLayer:
public string ApproveComment(int rowId, string userName)
{
if (userName == _repository.GetInsertedCommentUserName()) {
return "You cannot approve your own comment.";
}
if(_repository.ApproveComment(rowId, userName)){
return "Comment approved";
}
}
OR
public string ApproveComment(int rowId, string userName)
{
if (userName == _repository.GetInsertedCommentUserName()) {
throw new Exception("You cannot approve your own comment.");
}
if(_repository.ApproveComment(rowId, userName)){
return "Comment approved";
}
}
What is a clean and elegant way to return an appropriate message to the user, without using exceptions?
Or is my thinking wrong, is the 'exceptional' circumstance from the users point of view? IE., "I am expecting to get a product returned when I pass in this ID, but alas it doesn't exist!" From a developers/testers point of view I don't think this would be an exceptional case but from an end user's point of view - maybe.
You're asking two different questions. So let's answer them one by one.
The 'not found' problem
In the first case, the client is trying to access a product that does not exist. The appropriate status code to return from the server is 404, not found. For the client, this is an exceptional case actually, because it is probably trying to access an existing resource. In the 'fail' part of your client side javascript you can then check why the request failed (4xx range = client side error, 5xx range = server side error), and show appropriate messages to the user.
The 'approve' problem
For the approve problem you are returning unuseful statuscodes. You should check whether the resource the client is trying to approve exists before approving. If the resource does not exist, this is a client side error and you should return 404, not found.
If somehow the updating of the resource by the business logic layer still fails, and this is a server issue, you should return 500, internal server error. For any situation where it is the client's fault the update fails, return a statuscode in the 4xx range. (like 403, unauthorized, if the client is not allowed to approve its own comments)
Approving your own comments - This seems like an invalid operation. There is an exception for that.
And it is a bad request from the client then. There is a response status code for that.
There is nothing wrong with having Exceptions in your code, if you handle them consequentially. And there is nothing wrong with giving out 4xx status codes either, if you handle them gracefully on client side. It is a request the client should be unallowed to make, so it should fail, shouldn't it?
Your second example is not equivalent of the first. In the second example, you certainly will fail to achieve something. This is a Bad Request.
The first example, well, you try to achieve something that is not forbidden by default - getting a record with a given id. And it happens to be the case that this record does not exist. This is a case where your item was Not Found.
You can use HttpResponseMessage to customize the response message. It allows setting status code, reason phrase, message, and many other things.
Related
My .Net MVC application is attempting a an Http put call to update an existing record. I have noticed the controller put logic is not being triggered like other http communications.
I would like to include the HandleError logic found on Angular's Communication Page to write out the errors. When I include the error handler in my data service layer I get Argument of type 'Observable<never>' is not assignable to parameter of type '(err: any, caught: Observable<Record>) => ObservableInput<any>'
From what I see on I have the correct JSON object and API url. The controller can be reached if I copy the JSON object and URL into Postman.
Any insight provided on error handling and logging would be greatly appreciated.
Component logic:
updateRecord(record_id: number, newRecord: any): void
{
this.recordService.put<Record>(record_id, newRecord);
}
Data service logic:
put<Record>(record_id: number, record: Record): Observable<Record> {
var url = this.baseUrl + `api/record/${record_id}`;
let output = this.http.put<Record>(url, record, {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
})
.pipe(
catchError(this.handleError('put<Record>', record))
);
return output;
}
Handle Error:
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
consol e.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong.
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// Return an observable with a user-facing error message.
return throwError(
'Something bad happened; please try again later.');
}
Controller logic:
[HttpPut("{id}")]
public async Task<ActionResult<Domain.Record>> Put(int id, [FromBody] Domain.Record record)
{
//Confirm the request record and ID record being update match
if (id != record.record_id)
return BadRequest();
//Modify the state
_context.Entry(record).State = EntityState.Modified;
//Update the records in DB.records, throw appropriate error if there is one.
try
{
await _context.SaveChangesAsync();
}
catch(DbUpdateConcurrencyException)
{
if (!RecordExists(record.record_id))
return NotFound();
else
throw;
}
//return 200 OK
return NoContent();
}
It looks like you were missing a subscribe somewhere, per the comments on the main question.
In RXJS an Observable that is not subscribed to will never execute. So, this:
updateRecord(record_id: number, newRecord: any): void
{
this.recordService.put<Record>(record_id, newRecord);
}
Should be turned into this:
updateRecord(record_id: number, newRecord: any): void
{
this.recordService.put<Record>(record_id, newRecord).subscribe((result) => {
// process results here
};
}
Without the subscribe, I do not believe you'll get any results from the HTTP call inside that recordService.put() from.
I tried to solve this for hours now and I can not find anything. Basicly I have a simple controller which roughly looks like this:
[Route("v1/lists")]
public class ListController : Controller
{
...
[HttpPost("{id}/invite")]
public async Task<IActionResult> PostInvite([FromBody] string inviteSecret, [FromRoute] int id, [FromQuery] string userSecret)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
List list = await context.Lists.SingleOrDefaultAsync(l => l.ID == id);
if (list == null)
{
return NotFound();
}
User postingUser = await context.Users.SingleOrDefaultAsync(u => u.ID == list.CreationUserID);
if (postingUser == null || postingUser.Secret != userSecret)
{
return Forbid();
}
await context.ListInvites.AddAsync(new ListInvite{ListID = id, InviteSecret = inviteSecret});
await context.SaveChangesAsync();
return Ok();
}
....
}
The thing is: Whenever this method gets called and it exits through return Forbid();, Kestrel throws an InvalidOperationException afterwards with the message
No authentication handler is configured to handle the scheme: Automatic
(and of course the server returns a 500). What's strange about it is the fact that I am not doing any authentication whatsoever anywhere, and it does not happen e.g. if the method leaves with return Ok();. I'm really lost at this point because if you try to google this problem you get solutions over solutions... for people who actually do auth and have a problem with it. I really hope someone over here knows how to resolve this and/or what I could do to find out why this happens.
Like SignIn, SignOut or Challenge, Forbid relies on the authentication stack to decide what's the right thing to do to return a "forbidden" response: some authentication handlers like the JWT bearer middleware return a 403 response while others - like the cookie middleware - prefer redirecting the user to an "access denied page".
If you don't have any authentication handler in your pipeline, you can't use this method. Instead, use return StatusCode(403).
I build this API that takes in Id if that Id is found It will return the data. But if that Id is not found I return 409. When I test it in postman I see the status as 409 is that correct? is that all I need or should it also return some text in the body?
[HttpGet]
public IHttpActionResult Read(string type, string id)
{
if (id== null)
{
var msg = new HttpResponseMessage(HttpStatusCode.NotFound) { ReasonPhrase = "Unable to Find Id" };
msg.Content= new StringContent($"No entity with {id} was found");
return ResponseMessage(msg);
}
}
You do see a "not found" text:
You don't see anything in the body, because your API doesn't send a body, just a HTTP header
Re your comment, and linking in GPW's advice, return something custom - let errors be errors, and have this foreseeable condition as an "OK but no" response, perhaps:
[HttpGet]
public ActionResult Read(string type, string id)
{
if (id == null)
return Json(new { status= "fail", message= "id parameter is required" });
else if (type == null)
return Json(new { status= "fail", message= "type parameter is required" });
var ent = dbcontext.Entity.FirstOrDefault(e => e.Type == type && e.Id == id);
if(ent == null)
return Json(new { status="fail", message= "No entity with that type/id was found" });
else
return Json(new { status="ok", entityName= ent.Name });
}
In one of our apps we do use HTTP errors to alter the behavior of the client - there's a promise chain at the client react app and we use returning an error to halt processing of the chain, parse the error out, then go back to parsing knowing the JSON is a slightly different shape. I'm not convinced it's a great way to do it as it's made the client more complex than it could have been but if you want to do that then sure, look at ways of returning meaningful http errors. Personally (and GPW alludes to it) even debugging, many times I've missed the return code in postman, got a blank response, been misled that something else is wrong than what is actually happening/ got a 404 and thought the back end couldn't find the entity when actually I'd got the URL wrong and was getting a 404 for a different reason etc
I have the following register method, and I swear I (manually) tested this a while back and noted that if a username already existed, the result simply had a false value for result.Succeeded and would append the error message to the ModelState (using the build in AddErrors(result) helper method). I'm pretty sure this method (Register(...)) comes out of the box with ASP.NET mvc 5, but I think I changed the user to include a username (whereas out of the box, the email is simply used as the username).
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Username, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Instead, I am currently getting the error as an EntityValidationError being thrown and uncaught.
I know I could simply catch this error and move on with my day, but I want to make sure that something else is causing this issue if it is not the correct behavior.
Update:
After creating a new MVC project, I can confirm that the typical behavior (when a duplicate username is registered) is that CreateAsync should return a result with a false value for result.Succeeded and an error message of "Username is taken" should be appended to the ModelState. Clearly something is amiss in my code or config, but I haven't the foggiest idea of where to start exploring. If it helps, I have been seeing EntityValidationErrors in other places of my code lately in situations that shouldn't warrant it either. See: Unable to SaveChanges on a db update. Weird lazy loading behavior possibly?
I found my own solution. As I mentioned, I had altered the user to include a username (as well as make email optional). A part of this task involved creating a custom user validator class. In the custom user validator's ValidateAsync method, I had forgotten to check if a username existed already (and did not belong to the user). Like so:
async Task<IdentityResult> IIdentityValidator<TUser>.ValidateAsync(TUser item)
{
var errors = new List<string>();
// ...
// Piece of code I have now added
var owner = await _manager.FindByNameAsync(item.UserName);
if (owner != null && !EqualityComparer<string>.Default.Equals(owner.Id, item.Id))
{
errors.Add($"Username {item.UserName} is already taken");
}
// End of code I added
// ...
return errors.Any()
? IdentityResult.Failed(errors.ToArray())
: IdentityResult.Success;
}
I believe the lesson learned for me is the difference between the App layer validation, where validation occurs in the CreateAsync method by the UserManager. In the case of the App layer validation, the error will present itself exactly as prescribed. If that layer of validation is omitted, and the DB faces the same constraint, then when the context is saved, it will throw its own error. In this case, a slightly more cryptic EntityValidationError.
I am using Web API 2.0 to create my own project.
My Api contain ability to Add , Get and book product.
I Want to handle exception but there is some issue I little bit confused.
I have a controller: ProdctController with action method AddProdct.
[Route("~/CKService.svc/author/add")]
[HttpPost]
public IHttpActionResult AddProduct(ProductRequest request)
{
ProductManager.AddProduct(request.ProductId, request.ProductName,
request.Price);
return Ok(new AddProductResponse()
{
Status = AddProductStatus.Success
};)
}
Now, if the product already exists, so the data access layer will throw DuplicateKeyException
I want to handle this excpetion and return response like this:
return Ok(new AddProductResponse()
{
Status = AddProductStatus.AlreadyExists
};)
Is this reasonable to return HTTP status code 200?
How can I do this without add catch DuplicateKeyException to my controller because i think this is not the right way.
Thanks
Here is one suggestion. (and I emphasis that it is just one of the many options you have available)
Refactor the manager to handle and return the status of adding the product and then have the action return the appropriate response code based on the status.
[Route("~/CKService.svc/author/add")]
[HttpPost]
public IHttpActionResult AddProduct(ProductRequest request) {
var status = ProductManager.AddProduct(request.ProductId, request.ProductName, request.Price);
var response = new AddProductResponse() {
Status = status
};
if(status == AddProductStatus.Success) {
return Ok(response);
}
return BadRequest(response);
}
Avoid return 200 OK for requests that have not completed as intended. That will mislead the client making the request.
HTTP 200 is not resonable - it indicates a successfull request. Use a 4xx (Error) Code instead. 409 (conflict) fits your situation of an already existing object.
See: HTTP response code when resource creation POST fails due to existing matching resource
To catch the exception is totally fine. An unhandled exception would result in a 500 error (internal server error) which is not meaningful by any means.
Instead you should catch the Exception and return a HTTP Error like this (Exception Handling ASP.NET) :
throw new HttpResponseException(
Request.CreateErrorResponse(HttpStatusCode.Conflict, message))