Web API 2.0 Exception Handling - c#

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

Related

how to set response when i am using IActionResult in return type with error message?

when try block send error to catch block it throw and response I got at frontend was {status:'error',error:500}.
Please explain me what is going when I Re-throw in catch block.
Is .NET is set response when I re-throw?
I want to set status message. how can I do?
I want to set status message. how can I do?
Well, you can bind custom status inside BadRequest() or Any other response class result. For instance:
public async Task<IActionResult> DownloadAttachedFile()
{
try
{
//Your code
}
catch (Exception ex)
{
return BadRequest(new { StatusMessage = "Your Messaage", StatusCode = 400 });
}
}
Note: Here StatusMessage, you can even concat your ex message as well. Can introduce new custom error class. You can also skip StatusCode = 400 because BadRequest itself will return that code but if you want to fetch easily from your frontend you can set as well.
Output:
In your frontend you would get the response as following:
{
"statusMessage": "Your Messaage",
"statusCode": 400
}
In addition, you can always customize your response class based on your requirement.

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.

Displaying Custom Server Side Error Message in Angular and .Net Core API

I have some rest API written in C# and the API is called from Angular (I am using version Angular 8). The call is fine and it is working fine. However, in case of any exception, I cannot display the customized error message in angular. For example, suppose I have a server side validation in C# which validates if the value of a field matches with the string "abc". If it does not match, it will throw an error and in UI (developed in Angular), I want to display the message
"Invalid String Specified".
My server side code is as below -
if (headerValues.Equals("abc")) {
throw new InvalidStringException("Invalid String specified", 999);
}
The Invalid InvalidStringException class is as below -
public class InvalidStringException : System.Exception
{
int status { get; set; }
public InvalidStringException() { }
public InvalidStringException(string message, int status) : base(message) {
this.status = status;
}
}
When that exception is thrown and caught in server side, it is available as 500 exception but could not print the custom message.
I am trying following code in Angular -
} catch (error) {
console.log("Error Status: ", error.status);
console.log("Error Status: ", error.message);
}
Please suggest how to handle that scenario.
The error object that your Angular app receives should be an instance of HttpErrorResponse
You could do something like this to handle http errors:
if (error instanceof HttpErrorResponse) {
if (!error.status) {
console.log(error.message || error.toString());
} else {
console.log(`error status : ${error.status} ${error.statusText}`);
switch (error.status) {
case 401:
this.router.navigateByUrl("/login");
break;
case 500:
this.router.navigateByUrl("/login");
console.log(`redirect to login`);
break;
}
}
} else {
console.error("Other Errors");
}
You are throwing an exception which is handled by C# exception handler and it will only return the custom error message specified in that handler.
To return a custom message, you need to return with http code like 4xx or 5xx.
new HttpResponseException(Request.CreateErrorResponse(System.Net.HttpStatusCode.Conflict, "Custom Message"));
Or you can return with 2xx and you have to parse this subscribe or then method e.g.
new System.Web.Http.Results.ResponseMessageResult(
Request.CreateResponse((HttpStatusCode)227, "Custom Error Message")
);
this.http.get().toPromise().then((response: any) => {
if (response.status == 227) {
return error;
} else {
return data;
}
return apiResponse;
}).catch(error => {
//nothing here
});
If throwing a exception is not really necessary, you can return status code 400 and a message using BadRequest:
if (headerValues.Equals("abc")) {
return BadRequest("Invalid String specified");
}
Are you explicitly catching the InvalidStringException in your .NET API controller and returning the custom message? If not, the response will be a generic HTTP 500 'Internal Server Error' response. I'd suggest explicitly catching the InvalidStringException in your .NET API controller and returning a 400 response with your custom message e.g.
try {
...
}
catch (InvalidStringException iex) {
return BadRequest(iex.message); // iex.message == Invalid String specified
}
When the InvalidStringException scenario occurs, This will return a HTTP 400 response with "Invalid String specified" as the response body. You should be able to log the error on Angular side as you're currently doing...
As other people have mentioned, you need to catch the exception and convert it to an appropriate HTTP response in your own code.
The reason for that is because if otherwise your exception is handled by ASP.NET Core using exception handling configuration you have, and it may vary:
With developer exception page
Usually in development, you will have code:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
What it does is when your environment is Development, it turns on a special page for developers to see information of unhandled exceptions. It is only in this case, you get the exception stacktrace as well as the exception message in the response body.
Without developer exception page
Conversely, if the exception page is turned off (usually off for Production environment), you will see nothing in the response body.
How to fix
Given exception handling in ASP.NET Core is a cross-cutting concern, I wouldn't use try...catch around everywhere InvalidStringException needs to be converted to HttpResponse.
I would instead use either an IActionFilter or use UseExceptionHandler which is the exception handling middleware:
Here is an example of using UseExceptionHandler in Configure method in Startup.cs:
app.UseExceptionHandler(opt => opt.Run(
async ctx =>
{
var feature = ctx.Features.Get<IExceptionHandlerFeature>();
if (feature.Error is InvalidStringException ex)
{
await ctx.Response.WriteAsync(ex.Message);
}
}));
In this way, your InvalidStringException is handled globally in your application, without explicit try...catch. And you could throw the exception anywhere in your code, the above code would catch the exception and properly convert it to an HTTP response with your own message as the body.
Also note, because you are calling the API from an Angular app, so chances are you might need to set CORS up in your API application if the two applications run from different origins.
Without CORS, your HTTP request from the Angular app may fail before it can reach your API. In this case, the status of the HTTP response in your Angular app may be undefined. And in your console, you could see CORS errors.
you can use http interceptor to create general error handler for all http error in angular app,this way you can use alert ,redirect to login page in case token expired ,overwrite the error object and more but you can still access to the error object at the component level by add a callback for observable error.
Error Handler Service
#Injectable()
export class ErrorHandlerService implements HttpInterceptor {
constructor(private msgServ: MessageService) {}
public intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((err: HttpErrorResponse) => {
switch (err.status) {
case 500: {
this.msgServ.add({
severity: "error",
summary: "Error ",
detail: "Server is gone..💀"
});
break;
}
case 400: {
this.msgServ.add({
severity: "error",
summary: "Error ",
detail: "custome error message..."
});
break;
}
case 401: {
if (err.message == "invalid_token") {
// router 👉 navigate to login
}
break;
}
default: {
this.msgServ.add({
severity: "error",
summary: "Error ",
detail: err.message
});
}
}
return throwError(err);
})
);
}
}
add the Interceptor to Providers in app module
#NgModule({
....
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: ErrorHandlerService, multi: true },
MessageService
],
....
})
export class AppModule {}
demo 🚀
MessageService is related to primeng component library ,you can use your own alert structure

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.

Why is the enumeration WebExceptionStatus not intended to be used directly in my code?

The msdn page documenting the enumeration WebExceptionStatus says:
This API supports the product infrastructure and is not intended to be
used directly from your code.
Why? It is public and documented and an obvious way to determine why the webrequest failed.
Example:
I am using the class WebRequest get data from a webserver. Of course, I'll have to do exception handling if an error occurs during the request. So if something goes wrong, I want to seperate protocol errors (example: server returned a response with http status 400) from network errors (example: timout). For that I wanted the property Status:
try {
var request = WebRequest.CreateHttp(QueryUri);
//...
}
catch (WebException exception) {
if (exception.Status == WebExceptionStatus.ProtocolError) {
throw new MyCustomServerErrorException(...)
}
// ...
}

Categories