API Record not found - c#

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

Related

Postman won't POST to ASP.Net WebAPI 2 controller, but GET works

I can't seem to get my webapi to work in PostMan and it gives me a 404 when POSTing, but works only when using GET (even though the api specifically set to accept only POSTs! - go figure!)
Here's the controller code (that works) - NOTE: I can't use formdata as this is dotnet fw 4.72
[Route("api/GetProfile")]
[HttpPost]
public async Task<IHttpActionResult> GetProfile(string UserId)
{
var retval = new Profile();
if (UserId != null)
{
if (await dbv.IsValidUserIdAsync(UserId))
{
retval = await profile_Data.GetProfileAsync(UserId);
}
}
return Ok(retval);
}
The code works fine for GET (even though it's set to accept POST!), which it shouldn't.
In PostMan, the URI is
https://localhost:44371/api/GetProfile
The route is 100% correct!
On the Body tab, it is set to RAW and the following JSON is inside
{"UserId" : "69d40311-f9e0-4499-82ea-959949fc34fe"}
The parameter is 100% correct!
The error when attempting to POST is
{
"Message": "No HTTP resource was found that matches the request URI 'https://localhost:44371/api/GetProfile'.",
"MessageDetail": "No action was found on the controller 'Accounts' that matches the request."
}
If I put the parameters in the querystring, it works (even though the controller is set to accept POST).
If I change the controller to GET and PostMan to GET (and set the parameters in params), it works.
Is PostMan not compatible with ASP.Net webapi 2.0 ?
Why would GET work and POST not work? Makes no sense?
Try to set Content-Type application/json on postman and in your controller's POST method add the attribute FromBody
[FromBody] isn't inferred for simple types such as string or int. Therefore, the [FromBody] attribute should be used for simple types when that functionality is needed.
[Route("api/GetProfile")]
[HttpPost]
public async Task<IHttpActionResult> GetProfile([FromBody] string UserId)
{
var retval = new Profile();
if (UserId != null)
{
if (await dbv.IsValidUserIdAsync(UserId))
{
retval = await profile_Data.GetProfileAsync(UserId);
}
}
return Ok(retval);
}
Also consider to return CreatedAtAction or CreatedAtRoute instead of Ok

How to Log Http Communication Errors in Angular

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.

Returning appropriate messages to the calling client

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.

Web API 2.0 Exception Handling

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))

How can I return a BadRequest on Azure Mobile Services TableController GET template method?

I am using Azure Mobile Services (following the standard Azure TodoItems tutorial), and the most basic GET method that they provide is:
public IQueryable<MyModel> GetAllMyInfo()
{
return Query();
}
This works, but I am trying to extend it so that the method will only return MyModel data for an authenticated user (identified by the X-ZUMO-AUTH authentication header standard for Mobile Service API calls). So I modified the code for:
public IQueryable<MyModel> GetAllMyInfo()
{
// Get the current user
var currentUser = User as ServiceUser;
var ownerId = currentUser.Id;
return Query().Where(s => s.OwnerId == ownerId);
}
This also works when a valid auth token is passed. However, if an invalid auth header is passed, then the currentUser is null, and the query fails (obviously). So I am trying to check for null and return a BadRequest or a 403 HTTP code. Yet a simple `return BadRequest("Invalid authentication") gives a compilation error:
public IQueryable<MyModel> GetAllMyInfo()
{
// Get the current user
var currentUser = User as ServiceUser;
if(currentUser == null) {
return BadRequest("Database has already been created."); // This line gives a compilation error saying I need a cast.
}
var ownerId = currentUser.Id;
return Query().Where(s => s.OwnerId == ownerId);
}
Does anyone know how to check for a valid authentication token and return a 403 on this method (which wants an IQueryable return type?
You can use the [AuthorizeLevel] attribute on this method to indicate that a valid token must be present in order for the method to be invoked. It will return a 401 if not.
So your full method would be:
[AuthorizeLevel(AuthorizationLevel.User)]
public IQueryable<MyModel> GetAllMyInfo()
{
// Get the current user
var currentUser = User as ServiceUser;
var ownerId = currentUser.Id;
return Query().Where(s => s.OwnerId == ownerId);
}
Please note that for the Azure Mobile Apps SDK (not Mobile Services), the above attribute is simply replaced with [Authorize].
I know this is a bit late, but will document here for you and others that may come looking for a similar problem.
(While agreeing with Matt that a 403 could/should be achieved with a [Authorize] attribute, the question is regarding returning a different HttpStatusCode OR IQueryable)
I had a similar scenario where I needed to validate some query parameters and either return my results or a HttpError (in my case I wanted a 404 with content).
I found 2 ways, either keeping the return as IQueryable<T> and throwing a HttpResponseException or changing the return to IHttpActionResult and returning normal with HttpStatusCode or Ok(Data).
I found to prefer the later as throwing an Exception would be breaking the execution while in debug and not a very pleasant development experience.
Option 1 (Preferred)
//Adding Return annotation for API Documentation generation
[ResponseType(typeof(IQueryable<MyModel>))]
public IHttpActionResult GetAllMyInfo()
{
// Get the current user
var currentUser = User as ServiceUser;
if(currentUser == null) {
return BadRequest("Database has already been created.");
}
var ownerId = currentUser.Id;
return Ok(Query().Where(s => s.OwnerId == ownerId));
}
Option 2 (Throwing Exception)
public IQueryable<MyModel> GetAllMyInfo()
{
// Get the current user
var currentUser = User as ServiceUser;
if(currentUser == null) {
throw new HttpResponseException(System.Net.HttpStatusCode.BadRequest)
// Or to add a content message:
throw new HttpResponseException(new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.BadRequest) {
Content = new System.Net.Http.StringContent("Database has already been created.")
});
}
var ownerId = currentUser.Id;
return Query().Where(s => s.OwnerId == ownerId);
}

Categories