Inconsistent error response in .NET 6 Web API application - c#

I am not able to find how to throw exceptions as they are generated by .NET 6 Web API.
If I return BadRequest(ModelState) with added errors I am not getting same message with status, type, title etc.
By default .NET generates this kind of errors when validation error occurs:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-488f8c0057223cbba92fc1fbfc8865d8-2341d7aba29d098f-00",
"errors": {
"$": [
"The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. Path: $ | LineNumber: 7 | BytePositionInLine: 0."
],
"model": [
"The model field is required."
]
}
}
I want to configure my application to respond with the same error JSON, or I want to configure so it will respond with the custom JSON fields.
I tried to add a middleware that will catch exceptions, but it does not handle model errors (which are handled by framework by itself). How can I handle errors globally, or how can I throw exceptions that will be treated the same as framework handles them? Any documentation/tutorial links are welcome!

You can disable default bad request responses like the following code:
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
So you can return any model you want in BadRequest.
However, you then have to do the model validation yourself in each endpoint like:
[HttpPost(Name = "Post1")]
public IActionResult Post1(Model1 model)
{
if (!ModelState.IsValid)
return BadRequest(new CustomApiResponse());
...
}
[HttpPost(Name = "Post2")]
public IActionResult Post2(Model2 model)
{
if (!ModelState.IsValid)
return BadRequest(new CustomApiResponse());
...
}
If you want to return a global JSON structure, you can create a filter with the ActionFilterAttribute and use it for all your endpoints so you don't need to do model validation on every endpoint.
Custom Validation Filter:
public class CustomValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(new GlobalApiResponse());
}
base.OnActionExecuting(context);
}
}
You need to register your custom filter in Program.cs
builder.Services.AddControllers(options =>
{
options.Filters.Add(typeof(CustomValidationFilterAttribute));
});
This is just one of the ways you can use to achieve what you want, you can find different methods on the internet.

In .Net6 Web Api, If You Want To Validate Model By Your Code, And You Can Debug It, Just Do This:
public class ModelValidationFilterAttribute : ActionFilterAttribute
{
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(new BaseResponse() { code = ResponseCode.失败, msg = "" });
}
return base.OnActionExecutionAsync(context, next);
}
}
Then Do It In The Program.cs
// Add services to the container.
builder.Services.AddControllers(options =>
{
//Add Your Filter
options.Filters.Add<ModelValidationFilterAttribute>();
}).ConfigureApiBehaviorOptions(options =>
{
//Disable The Default
options.SuppressModelStateInvalidFilter = true;
});

Related

Custom error response for incorrect json. Dotnet Core Web API

Is there a mechanism for returning a custom error response if an invalid type is given to a WebApi in Dotnet Core?
E.G.
if I have a class that looks like this
public class SomeApiClass
{
public int Id { get; set; }
}
But make a post request like this (notice that I'm expecting an int and giving a string):
{
"id": "f"
}
Then the standard dotnet response looks like this:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-27be45d9cffab14698524a63120a4f88-6bfe2613f2328a42-00",
"errors": {
"$.id": [
"The JSON value could not be converted to System.Int64. Path: $.wmdaid | LineNumber: 1 | BytePositionInLine: 15."
]
}
}
However, I'd like all my responses to look the same for bad requests so that anybody implementing the API can always get a consistent JSON back. My problem being the JSON deserialisation is being done before the controller validation.
So, in a nutshell, Is it possible to change this response format as a part of the dotnet middleware?
You can use custom ActionFilter.
public class ReformatValidationProblemAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is BadRequestObjectResult badRequestObjectResult)
if (badRequestObjectResult.Value is ValidationProblemDetails)
{
context.Result = new BadRequestObjectResult("Custom Result Here");
}
base.OnResultExecuting(context);
}
}
Controller.cs
[ApiController]
[ReformatValidationProblem]
public class Controller : ControllerBase
{
...
}
or register it globally
Startup.cs
services.AddMvc(options =>
{
options.Filters.Add(typeof(ReformatValidationProblemAttribute));
});
You can also configure JsonOptions so that a generic message is displayed instead.
builder.Services.AddControllers().AddJsonOptions(o => o.AllowInputFormatterExceptionMessages = false);
This will preserve all the other fields from the automatic 400 response (from ValidationProblem) and display a generic error message for the malformed field: "The input was not valid."
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-190787f7ecae2a19b0e9c5da9c270fad-42237ddd1f920665-00",
"errors": {
"$.id": [
"The input was not valid."
]
}
}
You can implement your custom error model handler and replace it with the default ModelState object in each scenario such as bad request or ...
First of all change service provider to inject your custom error class like following :
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApiBehaviorOptions(options => {
options.InvalidModelStateResponseFactory = actionCtx => {
return CustomBadRequestHandler(actionCtx);
};
});
Next, we are going to create our custom Error Handler Class:
private BadRequestObjectResult CustomBadRequestHandler(ActionContext actionCtx) {
return new BadRequestObjectResult(actionCtx.ModelState
.Where(modelError => modelError.Value.Errors.Count > 0)
.Select(modelError => new Error {
ErrorFieldKey = modelError.Key,
ErrorDescription = modelError.Value.Errors.FirstOrDefault().ErrorMessage
}).ToList());
}
And finally, our custom error model that we want to return:
public class Error
{
//your custom filed that you want to show
public string ErrorFieldKey { get; set; }
public string ErrorDescription { get; set; }
}

Why do we no longer need to manually validate models in higher versions of ASP.NET Core?

All my models are automatically validated before hitting the endpoint, and return appropriate errors if some form of validation has failed.
I remember back in ASP.NET Core 2.2 we needed to manually call ModelState.IsValid to make sure an object has passed validation checks, but with the latest ASP.NET Core 3.0, this doesn't seem to be the case, and nowhere am I including/configuring any services explicitly for this behavior to exist.
Could someone shine some light on the matter, and perhaps link a relevant source where they mention this change?
EDIT: Is it due to the [ApiController] attribute? See: https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#automatic-http-400-responses
Thank you!
When using the [ApiController] attribute, you do not need to check ModelState.IsValid in each method since a 400 status code is automatically returned with the name of the fields where the validation failed, see https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-3.1
You can even modify the way the 400 status code should look like. This blog article should get you started: https://coderethinked.com/customizing-automatic-http-400-error-response-in-asp-net-core-web-apis/
Add this dependency:
services.AddMvc.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problems = new CustomBadRequest(context);
return new BadRequestObjectResult(problems);
};
});
Your custom bad request class can look like this. Create a YourErrorClass class if you want to include additional information regarding the errors, e.g., error severity, warnings etc.
public class CustomBadRequest
{
[JsonProperty("httpstatuscode")]
public string HttpStatusCode { get; set; }
[JsonProperty("errors")]
public List<YourErrorClass> Errors { get; set; } = new List<YourErrorClass>();
public CustomBadRequest(ActionContext context)
{
this.HttpStatusCode = "400";
this.Errors = new List<YourErrorClass>();
this.ConstructErrorMessages(context);
}
private void ConstructErrorMessages(ActionContext context)
{
foreach (var keyModelStatePair in context.ModelState)
{
var key = keyModelStatePair.Key;
var errors = keyModelStatePair.Value.Errors;
if (errors != null && errors.Count > 0)
{
foreach (var error in errors)
{
Errors.Add(new YourErrorClass()
{
ErrorMessage = error.ErrorMessage
// add additional information, if you like
});
}
}
}
}
Yes it is because of the ApiController attribute.
However this would happen even in Core 2.2, however there was an options to set the compatibility to 2.0, this however no longer works since 3.0, which might explain the behavior you see.
To turn it off in 3.0 and up, you need to set it in the options when doing services.AddControllers() or services.AddMvc() as in the following code:
services.AddControllers().ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
}
Also note that there was a difference between core 2.1 and later versions, as described in this MSDN article, as in the following examples:
Core 2.1 sample response:
{
"": [
"A non-empty request body is required."
]
}
Core 2.2 and up sample response:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|7fb5e16a-4c8f23bbfc974667.",
"errors": {
"": [
"A non-empty request body is required."
]
}
}
To get back the 2.1 behavior, you could set the compatibilty back to 2.1, but this no longer works in since 3.0 and up, instead you need the following code in the option configuration when doing services.AddControllers() or services.AddMvc() as in the following code:
services.AddControllers().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
new BadRequestObjectResult(new SerializableError(context.ModelState));
});

Triggering InvalidModelStateResponseFactory when ModelState is invalid

I am writing an API using asp.net core 3.0 and have configured my application with the following behavior for all controllers:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.ConfigureApiBehaviorOptions(setupAction =>
{
setupAction.InvalidModelStateResponseFactory = context =>
{
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Type = "https://courselibrary.com/modelvalidationproblem",
Title = "One or more model validation errors occurred.",
Status = StatusCodes.Status422UnprocessableEntity,
Detail = "See the errors property for details",
Instance = context.HttpContext.Request.Path
};
problemDetails.Extensions.Add("traceId", context.HttpContext.TraceIdentifier);
return new UnprocessableEntityObjectResult(problemDetails)
{
ContentTypes = { "application/problem+json" }
};
};
});
...
}
This works great with data annotations on my input (ex: [Required]) class's properties. It returns a 422 Unprocessable Entity reponse like this if any annotations fail:
{
"type": "https://courselibrary.com/modelvalidationproblem",
"title": "One or more model validation errors occurred.",
"status": 422,
"detail": "See the errors property for details",
"instance": "/api/songbooks/21924d66-dac6-43a5-beee-206de4d35216/songs",
"traceId": "0HLQFGSFIFL5L:00000001",
"errors": {
"Title": [
"The Title field is required."
]
}
}
However, I am implementing FluentValudation in my controller like this:
if (!newSong.IsValid)
{
newSong.Validate().AddToModelState(ModelState, null);
_logger.LogWarning("{method} failed model validation (ModelState: {#modelState}), returning Unprocessable Entity", nameof(Post), ModelState.Values.SelectMany(v => v.Errors));
return UnprocessableEntity(ModelState);
}
However, this doesn't trigger the InvalidModelStateResponseFactory like the built-in validation does.
Does anyone know how I can trigger the same sort of event from within my controller to use this convenient handler?
In fact, there is a pretty simple answer. Rather than returning an ActionResult of Ok() or BadRequest() or whatnot, by using this ValidationProblem pattern, you can simply return ValidationProblem(ModelState) and it'll use the factory set up in the Startup.cs Api configuration!
So here's what I can do instead:
if (!newSong.IsValid)
{
newSong.Validate().AddToModelState(ModelState, null);
_logger.LogWarning("{method} failed model validation (ModelState: {#modelState}), returning Unprocessable Entity", nameof(Post), ModelState.Values.SelectMany(v => v.Errors));
return ValidationProblem(ModelState);
}
In addition, you'll need to override the behavior to pull it out from your startup like this (Thanks to Kevin Dockx for this idea):
public override ActionResult ValidationProblem([ActionResultObjectValue] ModelStateDictionary modelStateDictionary)
{
var options = HttpContext.RequestServices.GetRequiredService<IOptions<ApiBehaviorOptions>>();
return (ActionResult)options.Value.InvalidModelStateResponseFactory(ControllerContext);
}

How to add value to the default exception response in ABP?

I want to add an ID (GUID) to the exception and:
Log it
Return it to the client json response
Where should I generate this log ID value and add it to the exception message that is logged. And where to change the following default response?
{
"targetUrl": null,
"result": null,
"success": false,
"error": {
"message": "An internal error occurred during your request!",
"details": "..."
},
"unAuthorizedRequest": false
}
I am using .NET Core version.
If you want to disable displaying the message for a particular AJAX call, add abpHandleError: false into the abp.ajax options.
Or you can disable the default behavior of the framework exception wrapper
public class PeopleController : AbpController
{
[HttpPost]
[WrapResult(WrapOnSuccess = false, WrapOnError = false)]
public JsonResult SavePerson(SavePersonModel person)
{
//TODO: save new person to database and return new person's id
return Json(new {PersonId = 42});
}
}
https://aspnetboilerplate.com/Pages/Documents/Javascript-API/AJAX?searchKey=wrap#asp-net-mvc-controllers
Another thing is; you can send exception details to the client by the below configuration
...
using Abp.Web.Configuration;
...
public override void PreInitialize()
{
Configuration.Modules.AbpWebCommon().SendAllExceptionsToClients = true;
}
...
https://aspnetboilerplate.com/Pages/Startup-Configuration#configuring-modules
Result Wrapping & Exception Handling:
ASP.NET Boilerplate does not wrap Web API actions by default if an action has successfully executed. It, however, handles and wraps exceptions. You can add the WrapResult/DontWrapResult attributes to actions and controllers for finer control. You can change this default behavior from the startup configuration (using Configuration.Modules.AbpWebApi()...). See the AJAX document for more info about result wrapping.
https://aspnetboilerplate.com/Pages/Documents/Web-API-Controllers?searchKey=wrap#result-wrapping-exception-handling
Wrapping Results
ASP.NET Boilerplate wraps the return values of dynamic Web API actions using an AjaxResponse object. See the ajax documentation for more information on wrapping. You can enable/disable wrapping per method or per application service. See this example application service:
public interface ITestAppService : IApplicationService
{
[DontWrapResult]
DoItOutput DoIt(DoItInput input);
}
https://aspnetboilerplate.com/Pages/Documents/Dynamic-Web-API?searchKey=wrap#wrapping-results
Lastly you can write your own ResultWrapperHandler...
public class CustomResultWrapperHandler : ResultWrapperHandler, ITransientDependency
{
//...
protected override void WrapResultIfNeeded(HttpRequestMessage request, HttpResponseMessage response)
{
//...
base.WrapResultIfNeeded(request, response);
}
}
if you want to get special message in some case you can use
throw new UserFriendlyException("your message");
the above code just effects on error message and doesn't show the details.
so its good option for production version.

Error Handling for ASP.NET Odata Web API

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.

Categories

Resources