4

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?

1
  • You need to replace the default model binder/input formatter for JSON requests to intercept & replace the error message before it reaches the controller action. learn.microsoft.com/en-us/aspnet/core/mvc/models/… Commented Jul 22, 2021 at 13:23

4 Answers 4

7

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));
});
Sign up to request clarification or add additional context in comments.

Comments

2

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."
        ]
    }
}

2 Comments

Why is it not working for me? I use System.Text.Json version 6.0.8. The AllowInputFormatterExceptionMessages flag does not seem to change much: github.com/dotnet/aspnetcore/blob/… Setting this flag to true or false results in the same standard message.
Because AllowInputFormatterExceptionMessages is a NewtonSoft.Json flag.
0

.NET 6.0

You can do the following:

  1. Create a custom validation attribute: PositiveInt32ValidationAttribute.cs.
  2. Create a custom result filter: MyApiResultFilter.cs.
  3. Register this result filter in Program.cs.

Here is a sample example:

PositiveInt32ValidationAttribute.cs

public class PositiveInt32ValidationAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(Object? value, ValidationContext validationContext)
    {
        var memberNames = new List<String> { validationContext.MemberName! };
        var errorMessage = String.Format("Invalid {0}.", validationContext.MemberName);
        if (value is Int32 id && id > 1)
            return ValidationResult.Success;
        return new ValidationResult(errorMessage, memberNames);
    }
}

MyApiResultFilter.cs

public class MyApiResultFilter : IResultFilter
{
    public void OnResultExecuted(ResultExecutedContext context)
    {

    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            var count = context.ModelState.Count;
            var errors = new Dictionary<String, String[]>(count);
            foreach (var keyModelStatePair in context.ModelState)
            {
                var key = keyModelStatePair.Key;
                var modelErrors = keyModelStatePair.Value.Errors;
                if (modelErrors is not null && modelErrors.Count > 0)
                {
                    var errorMessages = modelErrors.Select(error => error.ErrorMessage).ToArray();
                    errors.Add(key, errorMessages);
                }
            }

            var response = new
            {
                StatusCode = StatusCodes.Status400BadRequest,    //namespace Microsoft.AspNetCore.Http
                IsSuccess = false,
                Result = errors
            };
            context.Result = new BadRequestObjectResult(response);
        }
    }
}

Registering the MyApiResultFilter in Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services
    .AddControllers(config => config.Filters.Add(new MyApiResultFilter())
    .AddNewtonsoftJson(options =>    //I am using it to get the response in snake_case JSON format. You don't have to do this.
    {
        options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
        options.SerializerSettings.ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() };
    });

Result

{
    "status_code": 400,
    "is_success": false,
    "result": {
        "id": [
            "Invalid id."
        ]
    }
}

Screenshot

Result

Comments

-1

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; }         
}  

1 Comment

This appears to be validating the model state of the class. The issue is the error that is returned comes before the model state

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.