I have created by some example the ErrorController, which is handling Exception. Currently I have this:
[Route("[controller]")]
[ApiController]
[Authorize]
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorController : ControllerBase
{
public IActionResult ServerError()
{
var feature = HttpContext.Features.Get<IExceptionHandlerFeature>();
ErrorResponse response;
if (feature != null && feature.Error.GetType() == typeof(HttpResponseException))
{
response = new ErrorResponse
{
Error = "Error during processing request",
Message = feature.Error.Message
};
HttpContext.Response.StatusCode = ((HttpResponseException) feature.Error).HttpCode;
}
else
{
response = new ErrorResponse
{
Error = "Unexpected Server Error",
Message = feature?.Error.Message
};
}
return Content(JsonSerializer.Serialize(response), "application/json");
}
}
So whenever I throw in my method in controllers HttpResponseException, It will read it, and create response with corresponding code. But doing it through this, will log the HttpResponseException, which is not desired behaviour.
I have found solutions with Request.CreateResponse(), but that method does not exists, but when I replicated this method by myself, it is not desired behaviour (because of Swashbuckle/Swagger UI - The returning type is not the model object, but the HttpResponseMessage).
Found also something about ExceptionFilterAttribute, where I produced this code:
public class HttpExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (!(context.Exception is HttpResponseException)) return;
var exception = (HttpResponseException) context.Exception;
context.Result = new ObjectResult(""){StatusCode = exception.HttpCode};
}
}
But can't tell where to globally registered (based on this article).
So how to correctly manage returning desired object or some error with code, in a way that it will not be logged as warn?
Filters are registered globally in the Startup.cs via the options of AddMVC or AddControllersWithViews options.
Another way to handle exceptions globally is using an exception handling middleware which would catch also unexpected exceptions (which is my preferred way).
Related
How to catch this error in the back-end C# code?
See the solution usedat the end of the question description.
It is due to integer overflow for a HTTP GET URL where the URL contains an integer parameter. Calls with a 32 bit integer sized value work (e.g., "1234" works and the API is called).
HTTP GET www.domain.net/api/product/{productIdInteger}
Where the JavaScript caller calls HTTPS
www.domain.net/api/product/222222222222222222222222222222222222
The response looks like:
{
"errors": {
"productIdInteger": [
"The value '222222222222222222222222222222222222' is not valid."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-f6a8a724853f3f44ba9fc6468a42ad94-aca067537e73b645-00"
}
Startup.cs adds a middleware to catch exceptions at the WebAPI controller level.
This does not catch the URL parameter parsing error or a bad JSON in HTTP Body parsing error
Startup.cs has app.UseMiddleware();
MyErrorMiddleware has this invoke method and MyExceptionHandlerAsync is never called for this case.
public async Task Invoke(HttpContext context)
{
try
{
//This line gets called
await _next(context);
}
catch (Exception ex)
{
//The catch block is not executed if the integer passed to the controller method overflows a 32 bit integer
//The catch block is executed if an C# WebAPI controller method is called and the controller method throws an exception
await MyExceptionHandlerAsync(context, ex);
}
}
Here is what worked best for our application.
Create base class for controller methods (code below) having a Boolean errorFlag and a List ErrorList
Derive the return objects for controller methods off the base class in #1. Each of the return classes had the error properties plus the data returned from the controller method. The data property returned for the controller would be called "ReturnData" to make the front-end JavaScript easier.
Add the model validation error handler to Startup.cs. Return the same base class as in step #1
Add in the error handling middleware catch block, code to return the error using the same error class from step #1
This handles 1) business error from controller (not a thrown exception), 2) exception thrown from controller or a method it calls, no need to have a try/catch in the controller, 3) error passing data to the WebAPI controller method (model validation error)
public class ReturnBaseClass
{
public ReturnBaseClass() { errorList = new List<string>()}
public bool errorFlag {get;set;}
public List<string> errorList {get;set;}
}
//A controller return data could look like
public class CourseDataReturn : ReturnBaseClass
{
//Always called ReturnData to make front-end JavaScript easier
public CourseInfo ReturnData {get;set;}
}
//CourseInfo is the data returned from the data repository method called from the controller.
public class CourseInfo
{
public int CourseId {get;set;}
public string CourseName {get;set;}
}
We simplified our return HTTP status codes to OK, Bad Request, Internal Server Error for back-end WebAPI calls which are made. All other HTTP status codes would be a HTTP transport error (timeout, not found (URL is bad), ....
That got us past the REST mismatch of HTTP NotFound being either the URL is not found, such as domain name does not exist, and the database row being retrieved for a HTTP GET student by student ID not finding the student.
It is not an exception. It is ModelState not valid.
So you can catch this error by:
Step 1:
Add ConfigureApiBehaviorOptions in Program.cs file:
builder.Services.AddControllers().ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
});
Step 2:
Handle errors in top of Controller file:
if (!ModelState.IsValid) {
var errors = new List<string>();
foreach (var state in ModelState)
{
foreach (var error in state.Value.Errors)
{
errors.Add(error.ErrorMessage);
}
}
// Do something to handle error list
}
The code below is based on [https://briancaos.wordpress.com/2019/11/29/net-core-catch-model-binding-exceptions/]
We used a variation on this code in the solution
//In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
... //all of the configuration for services, etc...
//Add this at the very end of ConfigureServices()
//Handle errors - model bind error, bad parameter passed to WebAPI endpoint, ...
services.Configure<ApiBehaviorOptions>(options =>
options.InvalidmsResponseFactory = (ac =>
{
var ms = ac.ms;
//Properties related to errors: ms.IsValid, s.ErrorCount, ms.ValidationState
//var errors = ms.Where(x => x.Value.Errors.Count > 0);
var errs = new ValidationProblemDetails(ac.ms);
var errList = new List<string>();
//errList.Add($"Detail: {errs.Detail}");
errList.Add($"Title: {errs.Title}");
//errList.Add($"URL: {errs.Instance}");
//errList.Add($"HTTP Status Code: {errs.Status}");
//errList.Add($"Error Type: {errs.Type}");
List<string> mlist = new List<string>();
foreach (string k in errs.Errors.Keys)
{
//build one message line <KEY>: <ERROR MESSAGE 1>, <ERROR MESSAGE 2>, ...
string m = $"{k}: ";
string[] value = errs.Errors[k];
if (value.Length > 0)
{
m = $"{m}{string.Join(",", value)}";
}
mlist.Add(m);
}
string r = "";
if (msgList.Any())
{
r = string.Join(",", msgList);
}
return new BadRequestObjectResult(r);
}
));
I'm working on a kind of web-api. The parameters of my controller requests is automatically deserialized to dtos with Json.Net. I'm trying to bubble up the Json.Net deserializing errors to my ApiMiddleware.
var mvcBuilder = services.AddMvc().AddNewtonsoftJson(options =>
{
options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
options.SerializerSettings.Error = (sender, args) =>
{
var message = args.ErrorContext.Error.Message;
args.ErrorContext.Handled = false;
throw new ValidationException(message);
};
});
I supposed that the exception will buble up and my middleware will able to catch it and log the url of the request and deserializing error. But Json.Net catches all the inner throws by himself, so the only thing that comes to my middleware is "Current error context error is different to requested error”-exception.
I've explored about a dozen of questions somehow connected with my problem, but there was no solution.
So, does anybody know how to throw the ValidationException outside or make the original deserializing-exception delievered to my middleware? Thanks
After reading the ASP.NET Core source code, it seems that NewtonsoftJsonInputFormatter is designed to suppress JsonExceptions and convert it into model state errors. One workaround to this issue in ASP.NET Core 3.1 is to set MvcNewtonsoftJsonOptions.AllowInputFormatterExceptionMessages to false. This causes JsonExceptions to be captured on the model state, which you can then examine in an action filter and throw your own exceptions if you encounter one:
In Startup.cs:
services.AddControllers(c => { c.Filters.Add(new JsonErrorActionFilter()); })
.AddNewtonsoftJson(c => { c.AllowInputFormatterExceptionMessages = false; });
And the action filter could be something like this:
public class JsonErrorActionFilter : IActionFilter, IOrderedFilter
{
public void OnActionExecuted(ActionExecutedContext context) { }
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.Result == null && !context.ModelState.IsValid
&& HasJsonErrors(context.ModelState, out var jsonException))
{
throw new ValidationException(jsonException.Message);
}
}
private static bool HasJsonErrors(ModelStateDictionary modelState, out Exception jsonException)
{
foreach (var entry in modelState.Values)
{
foreach (var error in entry.Errors)
{
if (error.Exception is JsonException)
{
jsonException = error.Exception;
return true;
}
}
}
jsonException = null;
return false;
}
// Set to a large negative value so it runs earlier than ModelStateInvalidFilter
public int Order => -200000;
}
This is likely using MvcNewtonsoftJsonOptions.AllowInputFormatterExceptionMessages in a way that it's not intended to be used, so the solution will probably break if you upgrade to a different version of ASP.NET Core. But for now it may get the job done.
I created a global Exception handler middelware to catch all my custom exceptions.
When throwing an Exception in my DAL I expect that the middelware will catch it as the same type that it was thrown.
// API
[HttpGet]
[Route("api/users")]
public IActionResult Get(int id)
{
var user = _userService.GetById(id);
return Ok(user);
}
// Repository
public async Task<List<User>> GetById(int id)
{
throw new EntityNotFoundException("code", "message");
// .. return user
}
// Exception handler
public async Task Invoke(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception ex) // ex is of type JsonSerializationException
{
if (ex is EntityNotFoundException)
{
// Handle exception
}
}
}
In the above example the Exception is handled but is of type JsonSerializationException with an InnerException of type System.AggregateException that contains another InnerException with type EntityNotFoundException.
It seems that the Exception gets nested for each layer it gets passed along (DAL > Service > API). How can I avoid this so that I can catch the Exception as the original type?
The example you provided looks good but it lacks one important thing, which is single responsibility.
ASP.NET Core has a better approach, which is using exception filters, that can be registered globally too and can be written for each custom-exception and even for unhandled exceptions.
Sample:
public class EntityNotFoundExceptionFilter : IExceptionFilter
{
public EntityNotFoundExceptionFilter(// some dependencies that u want to inject)
{
...
}
public void OnException(ExceptionContext context)
{
if (!(context.Exception is EntityNotFoundException))
{
return;
}
context.ExceptionHandled = true;
context.Result = new NotFoundObjectResult // will produce 404 response, you can also set context.HttpContext.Response.StatusCode based on your exceptions statuscode and return an ObjectResult instead
{
context.Exception.Message
}
}
}
Now in your Startup.cs in the ConfigureServices(...) function add the following
public void ConfigureService(IServiceCollection services)
{
...
services.AddMvc(options =>
{
...
options.Filters.Add(typeof(EntityNotFoundExceptionFilter));
...
}
...
}
You will end up writing many filters but it is a cleaner approach and that is how the asp.net-core filterpipeline should be used + this will be working :)
I am not 100% sure why there are so many exceptions in your current implementation but my guess is that asp.net tries to return the exception and then fails to serialize it and stuff like that.
Edit:
I create a minimal example that can be found here. Just access the url via http://localhost:58741/api/some after cloning the project.
I have received the assignment to make our C# Webapi return always error responses with the following format:
{
"error": {
"code": 15,
"message": "My custom error message"
}
}
Because of that I registered my own ExceptionFilterAttribute:
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
class CustomError
{
public int code { get; set; }
public String message { get; set; }
}
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is BaseException)
{
BaseException exception = (BaseException)context.Exception;
HttpError error = new HttpError();
CustomError customError = new CustomError
{
code=exception.CustomError.code,
message=exception.CustomError.message
};
error.Add("error", customError);
context.Response = context.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, error);
}
else
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
This is working very well when the exception was throwed in a controller. But if the exception was throwed in an attribute (AuthorizeAttribute or EnableQueryAttribute), althoug my custom ExceptionFilter is called and the same code is executed, the response generated ignores the given HttpError and the response has the following body:
{
"error": {
"code": "",
"message": ""
}
}
I'm not very skilled in c#, I'm pretty sure that I'm doing something wrong, but I don't know what :S
Thanks in advance.
Edit 1:
I'm applying the Attribute that throws the exception at each method where the attribute is needed. For example I have an Odata Controller for an entity called "event":
[CustomAuthentication]
[CustomEnableQueryAttribute(PageSize = 20)]
public IQueryable<Event> Get()
{
(...)
return result;
}
As said, if the exception is throwed in the body of the controller, then my CustomExceptionFilter is invoked, and the custom response is created correctly.
But if the exception is thrown in the CustomAuthenticationAttribute or in the CustomEnableQueryAttribute, then altough my CustomExceptionFilter is invoked and the very same code is executed, the body response is wrong (see example).
Filter are mean for only controller but for global errors you need to use Global Error Filter in WebAPI.
to handle error thrown from attribute you need to Create Global Error Hnandler : https://learn.microsoft.com/en-us/aspnet/web-api/overview/error-handling/exception-handling
class OopsExceptionHandler : ExceptionHandler
{
public override void HandleCore(ExceptionHandlerContext context)
{
context.Result = new TextPlainErrorResult//you have to create this class
{
Request = context.ExceptionContext.Request,
Content = "Oops! Sorry! Something went wrong." +
"Please contact support#contoso.com so we can try to fix it."
};
}
}
there are a number of cases that exception filters can't handle. For example:
Exceptions thrown from controller constructors.
Exceptions thrown from message handlers.
Exceptions thrown during routing.
Exceptions thrown during response content serialization .
I'm interested in knowing what are the best practices being followed to raise exceptions in the ODataController.
If you raise an exception in the method it is translated to response code of 500 by default and the content has details on the error. I would like to be explicit of the response code and send 400 in cases of invalid key.
For example: If the input request has an invalid key would like to return a HttpResponseCode of 400 and content should have the error details similar to raising an exception.
Thanks a lot for your inputs
OData (at least since v3) uses specific json to represent errors:
{
"error": {
"code": "A custom error code",
"message": {
"lang": "en-us",
"value": "A custom long message for the user."
},
"innererror": {
"trace": [...],
"context": {...}
}
}
}
Microsoft .Net contains Microsoft.Data.OData.ODataError and Microsoft.Data.OData.ODataInnerError classes to form OData error on a server side.
To form proper OData error response (HttpResponseMessage), that contains error details you can:
1) form and return HttpResponseMessage in controller's action using System.Web.OData.Extensions.HttpRequestMessageExtensions.CreateErrorResponse method
return Request.CreateErrorResponse(HttpStatusCode.Conflict, new ODataError { ErrorCode="...", Message="...", MessageLanguage="..." }));
2) throw HttpResponseException using the same method for creating HttpResponseMessage
throw new HttpResponseException(
Request.CreateErrorResponse(HttpStatusCode.NotFound, new ODataError { ErrorCode="...", Message="...", MessageLanguage="..." }));
3) throw custom typed exception and convert it using Web Api action filters
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is CustomException)
{
var e = (CustomException)context.Exception;
var response = context.Request.CreateErrorResponse(e.StatusCode, new ODataError
{
ErrorCode = e.StatusCodeString,
Message = e.Message,
MessageLanguage = e.MessageLanguage
});
context.Response = response;
}
else
base.OnException(context);
}
}
Use HttpResponseException,
e.g. throw new HttpResponseException(HttpStatusCode.NotFound);.
Details can be found here.
For ASP.NET Core with OData, replace the EnableQueryAttribute on your Get method with a custom attribute that catches ODataException and throws a custom exception. In most cases, this allows standard error handling to step in as expected. Originally found this solution at https://github.com/OData/WebApi/issues/1898.
For your custom attribute, use something like the following:
public class CustomEnableQueryAttribute : EnableQueryAttribute
{
public override void ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions)
{
try
{
base.ValidateQuery(request, queryOptions);
}
catch (ODataException e)
{
throw new CustomException(e.Message, e){UserMessage = "Invalid OData query."};
}
}
}
On your Get method, use something like the following:
[HttpGet, CustomEnableQuery]
public virtual IQueryable<TDomainDto> Get()
{
return Repository.Get();
}
Check CreateErrorResponse in the OData documentation. The namespace to the used in Microsoft.AspNet.OData. I got my code working using this.