I have a sample code,
// EmpInfo.cs
try{
EmpData empData = readFromDB(empID);
}catch (Exception){
Logger.logError(“Fails to get data for empID: {0}”, empID);
throw;
}
How do I write the below function to handle the exception in EmpInfo.cs and show the custom 500 error page.
Since I am using MVC6, it has the bulletin OnActionExecuted function to handle the general error.
Please provide the concrete codes. Thanks!
//ExceptionHandlerMiddleware.cs
public class ExceptionHandlerMiddleWare
{
private readonly RequestDelegate next;
private readonly ILogger logger;
public ExceptionHandlerMiddleWare(RequestDelegate next, ILoggerFactory loggerFactory)
{
this.next = next;
this.logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleWare>();
}
public async Task Invoke(HttpContext context)
{
try {
await this.next.Invoke(context);
}
catch(Exception)
{
// How to use OnActionExecuted here????????
}
}
}
Related
I have a custom authorization policy where I'd like to output the reason to the output to help the consumer understand why he or she gets a 403 forbidden result. I have the following code:
internal class MyRequirementHandler : AuthorizationHandler<MyRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
if (myCondition)
{
context.Succeed(requirement);
}
else
{
context.Fail(new AuthorizationFailureReason(this, "Reason why failing"));
}
return Task.CompletedTask;
}
}
I would expect this to output "Reason why failing" to the body of the response, but I don't. I would get it to work by doing this instead:
if (context.Resource is DefaultHttpContext mvcContext)
{
mvcContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await mvcContext.HttpContext.Response.WriteAsync("Reason why failing");
}
But that doesn't feel like the correct way.
Grateful for any help and or input!
You can use IAuthorizationMiddlewareResultHandler to get AuthorizationFailureReason information. see Customize the behavior of AuthorizationMiddleware.
In my case, I use global exception middleware to handle exception, so I need to rethrow exception. You can see below example.
// Implement IAuthorizationMiddlewareResultHandler
public class ForbiddenAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler defaultHandler = new();
public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
{
if (authorizeResult.Forbidden)
{
var failureReasons = authorizeResult.AuthorizationFailure?.FailureReasons.FirstOrDefault();
throw new UnauthorizedAccessException(failureReasons?.Message ?? "You do not have permission to access.");
}
await defaultHandler.HandleAsync(next, context, policy, authorizeResult);
}
}
// Handle Global Exception
public class ExceptionHandleMiddleware
{
private readonly ILogger<ExceptionHandleMiddleware> _logger;
private readonly RequestDelegate _next;
public ExceptionHandleMiddleware(ILogger<ExceptionHandleMiddleware> logger, RequestDelegate next)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (System.Exception e)
{
_logger.LogError(e, "Catch some exception.");
// Handle UnauthorizedAccessException and get your "Reason why failing" message
}
}
}
// Don't forget Register DI
services.AddSingleton<IAuthorizationMiddlewareResultHandler, ForbiddenAuthorizationMiddlewareResultHandler>();
I develop a .NET Core 3 REST API using C#.
I read a lot about this topic, and I implemented a custom exception handler middleware, which is work well.
But I realized that the error messages are structured differently.
For example, I use .Net Core Identity and when I try to save a username which is already saved, then I get an HTTP 400 with the following error response: (Sorry about that, I can't post images.)
error Array(1){
0: {
code: "...",
description: ".."
}
}
And, I use too the Attribute validation, which gives this HTTPErrorResponse:
error: {
.
.
.
errors:{
Password: Array(1) {
0: "The field..."
}
}
}
Finally, my custom exception handler class gives an ErroDTO which contains a Code, and a description.
So the problem is there are 3 things, which give 3 different error responses, and I don't know how can I handle this, in Frontend which is an Angular project.
I would like to handle all errors and exceptions by my ErroDto class, but I don't know how to transform the Identity or Attribute errors.
I think that I could test the email and password validation directly in the endpoint instead of using Attribute, and I could test if the existing email, and the Identity won't be throw error. But I think it is not the best practice, and it will be a lot of boilerplate code.
This is my exception handling class:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
ErrorDto error = new ErrorDto();
if (ex is WrongCredentialsException)
{
code = HttpStatusCode.OK;
WrongCredentialsException wrongCredentialsException = (WrongCredentialsException) ex;
error.Code = wrongCredentialsException.Code;
error.Description = wrongCredentialsException.Message;
}
var result = JsonConvert.SerializeObject(new { error });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
Create custom Exception class
using System;
[Serializable]
public class EmployeeMgmtException : Exception
{
public EmployeeMgmtException(string message) : base(message)
{
}
}
Create an Exception Middleware class
public class ExceptionMiddleware
{
private readonly RequestDelegate next;
private readonly ILogger<ExceptionMiddleware> logger;
private readonly ResourceManager resourceManager;
}
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware>
logger)
{
this.next = next;
this.logger = logger;
this.resourceManager = new
ResourceManager("EmployeeException.API.Resources.Resource",
typeof(ExceptionMiddleware).Assembly);
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await this.next.Invoke(httpContext);
}
catch (Exception ex)
{
await this.HandleExceptionAsync(httpContext, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
ErrorResponse response = new ErrorResponse();
if (exception is EmployeeMgmtException)
{
context.Response.StatusCode = (int)HttpStatusCode.OK;
response.ErrorMessage = exception.Message;
}
else
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
response.ErrorMessage = this.resourceManager.GetString("FailedToProcess",
CultureInfo.CurrentUICulture);
}
this.logger.LogCritical(exception, exception.Message, GetProperties(context));
return context.Response.WriteAsync(response.ToString());
}
}
Register Exception Middleware filter in Startup.cs
app.UseMiddleware<ExceptionMiddleware>();
I am building an ASP.NET Core Api with a middleware for global exception handling. In Startup class, I configured some JSON options which are to be used for all controllers.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver()
{
NamingStrategy = new SnakeCaseNamingStrategy()
};
options.SerializerSettings.Converters.Add(new StringEnumConverter());
});
}
Then, I created a custom middleware for exceptions handling. The middleware is supposed to log the error and return error response as JSON. The problem is that global Newtonsoft's JsonConvert doesn't use the options which are specified during Mvc initialization (quite obvious).
Error handler middleware code:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, ILogger<ErrorHandlingMiddleware> logger)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, logger);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception ex, ILogger logger)
{
var errorResponse = new ErrorResponse { Message = ex.Message };
logger.LogError(ex, errorResponse.Message);
var result = JsonConvert.SerializeObject(errorResponse);
context.Response.ContentType = "application/json";
context.Response.StatusCode = 500;
return context.Response.WriteAsync(result);
}
}
The question is: Is there any way to use the same JSON serializer, which is used in controllers, in my custom middleware? I would like to avoid setting up the same configuration in two separate places.
JsonConvert.SerializeObject has an overload that also takes an instance of JsonSerializerSettings, which could be combined with the options pattern and dependency injection. All-in-all, that would look something like this:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly JsonSerializerSettings _jsonSerializerSettings;
public ErrorHandlingMiddleware(RequestDelegate next, IOptions<MvcJsonOptions> mvcJsonOptions)
{
_next = next;
_jsonSerializerSettings = mvcJsonOptions.Value.SerializerSettings;
}
...
private Task HandleExceptionAsync(HttpContext context, Exception ex, ILogger logger)
{
...
var result = JsonConvert.SerializeObject(errorResponse, _jsonSerializerSettings);
...
}
}
In this example, we're injecting the MvcJsonOptions instance that's been configured and then passing its SerializerSettings value through to JsonConvert.SerializeObject.
I'm developping a web API with ASP.NET Core and I'm trying to implement a custom error handling middleware so I can throw standard exceptions that can be converted into a JSON response with the appropriate HTTP Status code.
For example if I do:
throw new NotFoundApiException("The object was not found");
I need it to be converted into:
StatusCode: 404
ContentType: application/json
ResponseBody: {"error": "The object was not found"}
Here is my middleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try {
await next(context);
} catch (ApiException ex) {
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, ApiException exception)
{
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = exception.httpStatusCode;
return context.Response.WriteAsync(result);
}
}
Exceptions
public class ApiException : System.Exception
{
private int _httpStatusCode = (int)HttpStatusCode.InternalServerError;
public ApiException() { }
public ApiException(string message): base(message) { }
public int httpStatusCode {
get { return this._httpStatusCode; }
}
}
public class NotFoundApiException : ApiException
{
private int _httpStatusCode = (int)HttpStatusCode.BadRequest;
public NotFoundApiException() { }
public NotFoundApiException(string message): base(message) { }
}
Startup
public void Configure(/*...*/)
{
loggerFactory.AddConsole();
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseMvc();
}
Controller action
[HttpGet("object/{guid}")]
public WebMessage Get(Guid guid)
{
throw new NotFoundApiException(string.Format("The object {0} was not found", guid));
//...
I can see the request entering my registered middleware but the exception is not catched and simply thrown as usual.
I'm suspecting a race condition or something similar, I don't know very much about them async functions actually.
Has someone got an idea why my exception is not catched ?
edit By continuing the execution with VisualStudio I can see the expected behavior: I'm finally getting my response.
Seems like the Exception is not really catched by the middleware but somehow processed afterwards.
My solution to this problem was to remove app.UseDeveloperExceptionPage(); in Startup.cs
In my case, I found that app.UseMiddleware<ExceptionHandlingMiddleware>(); should be at the top of Configure() method.
You can try also Exception filters.
(of course, filters are not so flexible like as error handling middleware, which is better in general case, but - at least for me - filters are working fine without any issues)
That's what I'm using:
public class ExceptionGlobalFilter : ExceptionFilterAttribute
{
private readonly ILogger logger;
public ExceptionGlobalFilter(ILoggerFactory lf)
{
logger = lf.CreateLogger("ExceptionGlobalFilter");
}
public override void OnException(ExceptionContext context)
{
var customObject = new CustomObject(context.Exception);
//TODO: Add logs
if (context.Exception is BadRequestException)
{
context.Result = new BadRequestObjectResult(customObject);
}
else if (context.Exception is NotFoundException)
{
context.Result = new NotFoundObjectResult(customObject);
}
else
{
context.Result = new OkObjectResult(customObject);
}
base.OnException(context);
}
public override async Task OnExceptionAsync(ExceptionContext context)
{
await base.OnExceptionAsync(context);
return;
}
}
Startup.cs:
services.AddMvc(config =>
{
config.Filters.Add(typeof(ExceptionGlobalFilter));
});
More info:
Introduction to Error Handling in ASP.NET Core
Exception filters
Filters
MVC Issue #5594
ExceptionHandlerMiddleware.cs
In my case app.UseDeveloperExceptionPage(); was written in the Startup after the exception handler middleware. The fix was simply by moving the exception handler middleware to be after it.
#Pierre, I have met the same issue here when using Middleware as the global exception handler. The issue was caused by my mistake to wrote an "async void" method, I have throwed an exception in the method named "NewException":
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
NewException();
return Ok("<h1>Hi, Welcome!</h1>");
}
private async void NewException()
{
throw new InvalidOperationException("WTF");
}
The exception [InvalidOperationException("WTF")] will not be catching by the Middleware, if I change the code snippet to :
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
await NewException();
return Ok("<h1>Hi, Welcome!</h1>");
}
private async Task NewException()
{
throw new InvalidOperationException("WTF");
}
The exception Middleware will catch it. Hope this help.
In ASP.NET MVC 5 you could throw a HttpException with a HTTP code and this would set the response like so:
throw new HttpException((int)HttpStatusCode.BadRequest, "Bad Request.");
HttpException does not exist in ASP.NET Core. What is the equivalent code?
I implemented my own HttpException and supporting middleware which catches all HttpException's and turns them into the corresponding error response. A short extract can be seen below. You can also use the Boxed.AspNetCore Nuget package.
Usage Example in Startup.cs
public void Configure(IApplicationBuilder application)
{
application.UseIISPlatformHandler();
application.UseStatusCodePagesWithReExecute("/error/{0}");
application.UseHttpException();
application.UseMvc();
}
Extension Method
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseHttpException(this IApplicationBuilder application)
{
return application.UseMiddleware<HttpExceptionMiddleware>();
}
}
Middleware
internal class HttpExceptionMiddleware
{
private readonly RequestDelegate next;
public HttpExceptionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await this.next.Invoke(context);
}
catch (HttpException httpException)
{
context.Response.StatusCode = httpException.StatusCode;
var responseFeature = context.Features.Get<IHttpResponseFeature>();
responseFeature.ReasonPhrase = httpException.Message;
}
}
}
HttpException
public class HttpException : Exception
{
private readonly int httpStatusCode;
public HttpException(int httpStatusCode)
{
this.httpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode)
{
this.httpStatusCode = (int)httpStatusCode;
}
public HttpException(int httpStatusCode, string message) : base(message)
{
this.httpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode, string message) : base(message)
{
this.httpStatusCode = (int)httpStatusCode;
}
public HttpException(int httpStatusCode, string message, Exception inner) : base(message, inner)
{
this.httpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode, string message, Exception inner) : base(message, inner)
{
this.httpStatusCode = (int)httpStatusCode;
}
public int StatusCode { get { return this.httpStatusCode; } }
}
In the long term, I would advise against using exceptions for returning errors. Exceptions are slower than just returning an error from a method.
After a brief chat with #davidfowl, it seems that ASP.NET 5 has no such notion of HttpException or HttpResponseException that "magically" turn to response messages.
What you can do, is hook into the ASP.NET 5 pipeline via MiddleWare, and create one that handles the exceptions for you.
Here is an example from the source code of their error handler middleware which will set the response status code to 500 in case of an exception further up the pipeline:
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ErrorHandlerOptions _options;
private readonly ILogger _logger;
public ErrorHandlerMiddleware(RequestDelegate next,
ILoggerFactory loggerFactory,
ErrorHandlerOptions options)
{
_next = next;
_options = options;
_logger = loggerFactory.CreateLogger<ErrorHandlerMiddleware>();
if (_options.ErrorHandler == null)
{
_options.ErrorHandler = _next;
}
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError("An unhandled exception has occurred: " + ex.Message, ex);
if (context.Response.HasStarted)
{
_logger.LogWarning("The response has already started,
the error handler will not be executed.");
throw;
}
PathString originalPath = context.Request.Path;
if (_options.ErrorHandlingPath.HasValue)
{
context.Request.Path = _options.ErrorHandlingPath;
}
try
{
var errorHandlerFeature = new ErrorHandlerFeature()
{
Error = ex,
};
context.SetFeature<IErrorHandlerFeature>(errorHandlerFeature);
context.Response.StatusCode = 500;
context.Response.Headers.Clear();
await _options.ErrorHandler(context);
return;
}
catch (Exception ex2)
{
_logger.LogError("An exception was thrown attempting
to execute the error handler.", ex2);
}
finally
{
context.Request.Path = originalPath;
}
throw; // Re-throw the original if we couldn't handle it
}
}
}
And you need to register it with StartUp.cs:
public class Startup
{
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerfactory)
{
app.UseMiddleWare<ExceptionHandlerMiddleware>();
}
}
Alternatively, if you just want to return an arbitrary status code and aren't concerned with the Exception-based approach, you can use
return new HttpStatusCodeResult(400);
Update: as of .NET Core RC 2, the Http prefix is dropped. It is now:
return new StatusCodeResult(400);
The Microsoft.AspNet.Mvc.Controller base class exposes a HttpBadRequest(string) overload which takes an error message to return to the client. So from within a controller action, you could call:
return HttpBadRequest("Bad Request.");
Ultimately my nose says any private methods called from within a controller action should either be fully http-context-aware and return an IActionResult, or perform some other small task completely isolated from the fact that it's inside of an http pipeline. Granted this is my personal opinion, but a class that performs some piece of business logic should not be returning HTTP status codes, and instead should be throwing its own exceptions which can be caught and translated at the controller/action level.
There is no equivalent in ASP.NET Core itself. As others have said, the way to implement this is with a middleware and your own exceptions.
The Opw.HttpExceptions.AspNetCore NuGet package does exactly this.
Middleware and extensions for returning exceptions over HTTP, e.g. as ASP.NET Core Problem Details. Problem Details are a machine-readable format for specifying errors in HTTP API responses based on https://www.rfc-editor.org/rfc/rfc7807. But you are not limited to returning exception results as Problem Details, but you can create your own mappers for your own custom formats.
It is configurable and well documented.
Here is the list of provided exceptions out of the box:
4xx
400 BadRequestException
400 InvalidModelException
400 ValidationErrorException<T>
400 InvalidFileException
401 UnauthorizedException
403 ForbiddenException
404 NotFoundException
404 NotFoundException<T>
409 ConflictException
409 ProtectedException
415 UnsupportedMediaTypeException
5xx
500 InternalServerErrorException
500 DbErrorException
500 SerializationErrorException
503 ServiceUnavailableException
Here is an extended version of #muhammad-rehan-saeed answer.
It logs exceptions conditionaly and disables http cache.
If you use this and UseDeveloperExceptionPage, you should call UseDeveloperExceptionPage before this.
Startup.cs:
app.UseMiddleware<HttpExceptionMiddleware>();
HttpExceptionMiddleware.cs
/**
* Error handling: throw HTTPException(s) in business logic, generate correct response with correct httpStatusCode + short error messages.
* If the exception is a server error (status 5XX), this exception is logged.
*/
internal class HttpExceptionMiddleware
{
private readonly RequestDelegate next;
public HttpExceptionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await this.next.Invoke(context);
}
catch (HttpException e)
{
var response = context.Response;
if (response.HasStarted)
{
throw;
}
int statusCode = (int) e.StatusCode;
if (statusCode >= 500 && statusCode <= 599)
{
logger.LogError(e, "Server exception");
}
response.Clear();
response.StatusCode = statusCode;
response.ContentType = "application/json; charset=utf-8";
response.Headers[HeaderNames.CacheControl] = "no-cache";
response.Headers[HeaderNames.Pragma] = "no-cache";
response.Headers[HeaderNames.Expires] = "-1";
response.Headers.Remove(HeaderNames.ETag);
var bodyObj = new {
Message = e.BaseMessage,
Status = e.StatusCode.ToString()
};
var body = JsonSerializer.Serialize(bodyObj);
await context.Response.WriteAsync(body);
}
}
}
HTTPException.cs
public class HttpException : Exception
{
public HttpStatusCode StatusCode { get; }
public HttpException(HttpStatusCode statusCode)
{
this.StatusCode = statusCode;
}
public HttpException(int httpStatusCode)
: this((HttpStatusCode) httpStatusCode)
{
}
public HttpException(HttpStatusCode statusCode, string message)
: base(message)
{
this.StatusCode = statusCode;
}
public HttpException(int httpStatusCode, string message)
: this((HttpStatusCode) httpStatusCode, message)
{
}
public HttpException(HttpStatusCode statusCode, string message, Exception inner)
: base(message, inner)
{
}
public HttpException(int httpStatusCode, string message, Exception inner)
: this((HttpStatusCode) httpStatusCode, message, inner)
{
}
}
I had better results with this code than with :
UseExceptionHandler:
automatically logs every "normal" exceptions (ex 404).
disabled in dev mode (when app.UseDeveloperExceptionPage is called)
cannot catch only specific exceptions
Opw.HttpExceptions.AspNetCore: logs exception when everything works fine
See also ASP.NET Core Web API exception handling
Starting from ASP.NET Core 3 you can use ActionResult to return HTTP status code:
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ITEMS_TYPE> GetByItemId(int id)
{
...
if (result == null)
{
return NotFound();
}
return Ok(result);
}
More details are here: https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1