I have a bit of middleware written that on a callback checks if the response is a 500. If it is a 500 I want to return the exception that was thrown. How do I get the exception that was thrown in the application?
Startup.cs
...
app.UseMiddleware<APIExceptionMiddleware>();
// Add MVC to the request pipeline.
app.UseMvc();
...
APIExceptionMiddleware.cs:
public class APIExceptionMiddleware
{
private readonly RequestDelegate _next;
public APIExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
context.Response.OnStarting(
callback: (state) =>
{
HttpResponse response = (HttpResponse)state;
if (response.StatusCode == 500)
{
// want to grab exception here turn it into JSON response place it in the response.Body but not sure how I access the exception.
return response.WriteAsync("An Error Occured");
}
return Task.FromResult<object>(null);
}, state: context.Response);
await _next.Invoke(context);
}
}
So as the request goes into UseMvc() I have an exception that is thrown. I can use app.UseDeveloperException(); and get a nice friendly HTML page with the stacktrace and exception.
I want to almost repeat that but make it a nice friendly JSON api response for my application. SO if a 500 is thrown I am using middleware to where I am going to turn that into a pretty json response and send it out as my response through the api request. My problem is how do I fetch this exception in the middleware?
If UseDeveloperException() is doing it shouldn't I be able to as well?
Take a look at the code for DeveloperExceptionPageMiddleware... in particular look at Invoke(HttpContext context) (shown below). Instead of using this default middleware that you are currently adding, use your own which you've started. It will be very much like DeveloperExceptionPageMiddleware: catch any exception, but instead of returning an error page just format your JSON response as desired.
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(0, ex, "An unhandled exception has occurred while executing the request");
if (context.Response.HasStarted)
{
_logger.LogWarning("The response has already started, the error page middleware will not be executed.");
throw;
}
try
{
context.Response.Clear();
context.Response.StatusCode = 500;
await DisplayException(context, ex);
if (_diagnosticSource.IsEnabled("Microsoft.AspNetCore.Diagnostics.UnhandledException"))
{
_diagnosticSource.Write("Microsoft.AspNetCore.Diagnostics.UnhandledException", new { httpContext = context, exception = ex });
}
return;
}
catch (Exception ex2)
{
// If there's a Exception while generating the error page, re-throw the original exception.
_logger.LogError(0, ex2, "An exception was thrown attempting to display the error page.");
}
throw;
}
}
Related
I tried to implement a global error handler on my Asp.net core mvc web page. For that I created an error handler middleware like described on this blog post.
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception error)
{
var response = context.Response;
response.ContentType = "application/json";
switch (error)
{
case KeyNotFoundException e:
// not found error
response.StatusCode = (int)HttpStatusCode.NotFound;
break;
default:
// unhandled error
response.StatusCode = (int)HttpStatusCode.InternalServerError;
break;
}
var result = JsonSerializer.Serialize(new { message = error?.Message });
await response.WriteAsync(result);
context.Request.Path = $"/error/{response.StatusCode}"; // <----does not work!
}
}
}
The middleware works as expected and catches the errors. As a result i get a white page with the error message. But i am not able to display a custom error page. I tried it with the following line of code. But this does not work.
context.Request.Path = $"/error/{response.StatusCode}";
Any ideas how I can achive my goal?
Thanks in advance
It seems that you wish to redirect the browser to an error page.
To do this you'll need to replace:
context.Request.Path = $"/error/{response.StatusCode}";
With
context.Reponse.Redirect($"/error/{response.StatusCode}");
Also, since you're sending a redirect, the response content needs to be empty, so remove the response.WriteAsync bit too.
var result = JsonSerializer.Serialize(new { message = error?.Message });
await response.WriteAsync(result);
I'm working on a Core 3.1 Web API and an MVC application that uses it. In the MVC app I have UserRepo set up containing an Update method:
public async Task<User> Update(User user)
{
HttpClient client = _clientFactory.CreateClient("namedClient");
HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));
try
{
response.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
if ((int)response.StatusCode == StatusCodes.Status409Conflict)
{
throw;
}
}
return await response.Content.ReadFromJsonAsync<User>();
}
The repo is injected into a service, and the service is injected into a controller which is where I'd like to handle the error.
The Update method is incomplete because I am trying to figure out how handle a 409 error which I return from API if the rowversion value was outdated. When response.EnsureSuccessStatusCode(); is called, an exception is thrown if it wasn't a success code. I imagined I could just have it bubble-up to the front-end and handle it in the controller action, but the exception object doesn't contain anything specific enough to identify that it's a 409 error:
So if this bubbles up to the controller action, best I could to is try to parse out the status code from the message, which seems like a bad idea.
I can find examples of people returning 409 codes from their Web APIs, but not how they would be handled in an MVC app when logic is separated into different classes instead of being all in one action.
How could I handle this? Do I create a custom exception and throw that? Maybe add additional data to the exception with ex.Data.Add() and read it in the action? Would that be a bad idea?
Thanks to suggestions from #Peter Csala, #Craig H, and #Filipe, this is the solution I settled on.
Method that calls the API:
public async Task<User> Update(User user)
{
HttpClient client = _clientFactory.CreateClient("namedClient");
HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));
await HttpResponseValidator.ValidateStatusCode(response);
return await response.Content.ReadFromJsonAsync<User>();
}
A static method I can reuse that will produce an exception during a concurrency error:
public static class HttpResponseValidator
{
public static async Task ValidateStatusCode(HttpResponseMessage response)
{
if ((int)response.StatusCode < 200 || (int)response.StatusCode > 299)
{
string errorResponse = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.Conflict)
{
DBConcurrencyException ex = new DBConcurrencyException(errorResponse);
throw ex;
}
}
}
}
The exception bubbles up all the way back to the action that called the method calling the API. I catch it in the action and handle it after logging the error.
I'm working on a Core 3.1 Web API and an MVC application that uses it. In the MVC app I have UserRepo set up containing methods that send requests to the API:
public class UserRepo : IUserRepo
{
private readonly IHttpClientFactory _clientFactory;
public UserRepo(IHttpClientFactory httpClientFactory)
{
_clientFactory = httpClientFactory;
}
public async Task<User> GetById(int Id)
{
// same code structure as Update ...
}
public async Task<User> Update(User user)
{
HttpClient client = _clientFactory.CreateClient("NamedClient");
try
{
HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));
return await response.Content.ReadFromJsonAsync<User>();
}
catch (Exception ex)
{
throw;
}
}
public async Task<User> Insert(User user)
{
// same code structure as Update ...
}
}
The Update method never throws errors like 400, 404, etc, that come back from the API, resulting in silent errors. I found that to cause exceptions I need to call response.EnsureSuccessStatusCode();, which worked.
However, the exception doesn't contain what I need to find out what went wrong with the API call. If a 400 error occurs, an exception will be thrown saying that 400 error occurred, but not why it occurred. The why is returned to the response variable and it may look something like this due to validation I have implemented:
{
"errors": {
"FirstName": [
"The FirstName field is required."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|502d647b-4c7425oa321c8c7b."
}
Is there a widely used way to handle the response that comes back after an error is produced in the API? I want to know why a 400 error occurred so I know what to fix. I just don't know what is the "right" way to handle these response messages.
One idea I had was to catch the exception and log it along with the response text every time before throwing it. Then when my app crashes I can go to the logs and read the message returned. The Update method would look like this:
public async Task<User> Update(User user)
{
HttpClient client = _clientFactory.CreateClient("NamedClient");
HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));
try
{
response.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
string errorMessage = await response.Content.ReadAsStringAsync()
_logger.LogError(ex, errorMessage);
throw;
}
return await response.Content.ReadFromJsonAsync<User>();
}
Another thought that came would be maybe it's possible to add the message to the exception itself and see it when it's thrown? Would it make sense to add the message as an inner exception?
Is there a widely used way to handle the response that comes back after an error is produced in the API? I want to know why a 400 error occurred so I know what to fix. I just don't know what is the "right" way to handle these response messages.
Generally, exception details are only logged, and not returned. This is because details may include personally identifiable information or technical details that could reveal potential security vulnerabilities. There is an error details RFC that is becoming more common, but even that should not have details like PII or a stack trace.
In the case of one API (the MVC endpoint) calling another API (the actual API), the MVC endpoint should return a code in the 5xx range. Either 500 or 502 would be acceptable here. All such errors should be logged on the server side along with their details.
Note that the default behavior is to return 500 if an exception is propagated, so keeping the throw; is all you really need to do. However, it's normal to do error logging in the "pipeline", e.g., middleware for ASP.NET Core or something like a globally-installed action filter for ASP.NET MVC. This is to ensure all errors are logged while avoiding repetition.
EnsureSuccessStatusCode throws an HttpRequestException if the StatusCode is different than 2xx.
In order to gain the most information from the response, you have to retrieve it manually.
The general flow could be described in the following way:
Issue the request inside a try-catch block.
If there was no exception then examine the response's statusCode.
If it is different than the expected one(s) then try to read the response's body
And log everything.
Step #1
HttpResponseMessage response = null;
try
{
response = await httpClient.PutAsync(...);
}
catch (InvalidOperationException ioEx)
{
//The request message was already sent by the HttpClient instance, but failed due to some protocol violation
HttpClient.CancelPendingRequests();
//TODO: logging
}
catch (TaskCanceledException tcEX)
{
//The request was not completed due to either it's timed out or cancelled
if(!tcEX.CancellationToken.IsCancellationRequested)
HttpClient.CancelPendingRequests();
//TODO: logging
}
catch (HttpRequestException hrEx)
{
//The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation.
//TODO: logging
}
Step #2
HttpStatusCodes[] validResponseCodes = new [] {
HttpStatusCode.OK,
HttpStatusCode.Created,
HttpStatusCode.NoContent,
};
if(!validResponseCodes.Contains(response?.StatusCode))
{
//Step #3
}
Step #3
string errorResponse = await response.Content.ReadAsStringAsync();
//Try to parse it if you know the structure of the returned json/xml/whatever
What is the proper way to do error handling for HTTP triggered Azure Functions v2? Should - as recommend in this answer - all inner exceptions be caught and nothing ever be thrown?
I.e. always surround everything you do inside your function with try-catch, like this:
[FunctionName("DemoHttpFunction")]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, ILogger log)
{
try
{
await InnerDoSomething();
return new NoContentResult();
}
catch(Exception ex)
{
log.LogError(ex, "Something went wrong");
return new StatusCodeResult(500);
}
}
Drawbacks I see with that are
No error message at all gets returned to the user. StatusCodeResult does not provide any overload to supply a message
All executions of your function will show as successful, e.g. in the logging in Application Insights.
No error message at all gets returned to the user. StatusCodeResult does not provide any overload to supply a message
You are in control of the code. You can easily use a different result that would include your desired information.
//...
catch(Exception ex)
{
log.LogError(ex, "Something went wrong");
var model = new { error = "User friendly something went wrong" };
return new ObjectResult(model) {
StatusCode = 500
};
}
//...
So I am looking for a pattern on how to handle exceptions. Specifically I want to be able to pass the exception message on to the client from a Web API controller.
The client is using a third party library which deals with a call to the API
as
this.msgs = [];
let xhr = new XMLHttpRequest(),
formData = new FormData();
for(let i = 0; i < this.files.length; i++) {
formData.append(this.name, this.files[i], this.files[i].name);
}
xhr.upload.addEventListener('progress', (e: ProgressEvent) => {
if(e.lengthComputable) {
this.progress = Math.round((e.loaded * 100) / e.total);
}
}, false);
xhr.onreadystatechange = () => {
if(xhr.readyState == 4) {
this.progress = 0;
if(xhr.status == 200)
this.onUpload.emit({xhr: xhr, files: this.files});
else
this.onError.emit({xhr: xhr, files: this.files});
this.clear();
}
};
xhr.open('POST', this.url, true);
xhr.send(formData);
My current call back function is such
errorComplete(event: any) {
console.log("upload error");
}
notice that on error the library just wraps up the XMLHttpRequest and passes it on to my call back function.
so in the controller I have created a test line as follows
throw new Exception("This is a test message");
This is to simulate an unexpected exception
currently the return code in the XMLHttpRequest is a 500 and the text is the html that .Net generates when an exception occurs.
yes the method in my controller will need to wrapper in a try-catch but I am not sure of what code to put in the catch so I can send the error message down to the client and it can handle it and not take down the application.
the current use case I am looking at is the user uploads a file to the system but there is already a file with the specified name in the system. And renaming the file is not an option! I need to notify the user that there is already a file with that name in the system.
google has not revealed a way to pass the message back so I can process it.
Thank you Nkosi- your comment got me on the right track.
I implemented some middleware.
public class UIExceptionHandler
{
RequestDelegate _next;
public UIExceptionHandler(RequestDelegate next)
{
this._next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await this._next(context);
}
catch (Exception x)
{
if (!context.Response.HasStarted)
{
context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
context.Response.Headers["Message"] = x.Message;
}
}
}
}
public static class UIExcetionHandlerExtensions
{
public static IApplicationBuilder UseUIExceptionHandler(this IApplicationBuilder builder)
{
return builder.UseMiddleware<UIExceptionHandler>();
}
}
and in the configure method of the startup
app.UseUIExceptionHandler();
then on the client I can do
errorComplete(event: any) {
var errorMessage = event.xhr.getResponseHeader('Message');
console.log(errorMessage);
}
If anyone sees an issue with this solution please let me know