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