[SOLVED] 400 badrequest when passing int instead string in web api .net core 3.1

Issue

I need custom message for my request.

Currently I am passing below request json from postman

{
    "countryid": "14sdsads02"
}

but my model is

public class model
{
    public int countryid {get;set;}
}

When pass request from postman, I am getting below error

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|21793cf-495c68ddbc92ab35.",
    "errors": {
        "$.countryid": [
            "The JSON value could not be converted to System.Int32. Path: $.countryid | LineNumber: 1 | BytePositionInLine: 29."
        ]
    }
}

But instead of this, I need custom error in my web api. How can I achieve this?

Solution

If you want to define a custom response in case of 400 then you can do the followings:

The handler

In order to catch the model binding errors you have to wire up a handler for the ApiBehaviorOptions‘s InvalidModelStateResponseFactory (1).

The required delegate is quite generic: Func<ActionContext, IActionResult>. So, first let’s create an interface for this:

public interface IModelBindingErrorHandler
{
    IActionResult HandleInvalidModelState(ActionContext context);
}

Here is a simple handler implementation:

public class ModelBindingErrorHandler : IModelBindingErrorHandler
{
    private ICorrelationContextAccessor correlationContextAccessor;
    private ILogger logger;

    public ModelBindingErrorHandler(ICorrelationContextAccessor correlationContextAccessor, ILogger logger)
    {
        this.correlationContextAccessor = correlationContextAccessor;
        this.logger = logger;
    }

    public IActionResult HandleInvalidModelState(ActionContext context)
    {
        string correlationId = correlationContextAccessor.CorrelationContext?.CorrelationId;

        var modelErrors = context.ModelState
            .Where(stateEntry => stateEntry.Value.Errors.Any())
            .Select(stateEntry => new InvalidData
            {
                FieldName = stateEntry.Key,
                Errors = stateEntry.Value.Errors.Select(error => error.ErrorMessage).ToArray()
            });

        logger.LogError("The request contained malformed input.", modelErrors);

        var responseBody = new GlobalErrorModel
        {
            ErrorMessage = "Sorry, the request contains invalid data. Please revise.", 
            ErrorTracingId = correlationId
        };
    
        return new BadRequestObjectResult(responseBody);
    }
}

The helper classes

public class GlobalErrorModel
{
    public string ErrorMessage { get; set; }
    public string ErrorTracingId { get; set; }
}

public class InvalidData
{
    public string FieldName { get; set; }
    public string[] Errors { get; set; }

    public override string ToString() => $"{FieldName}: {string.Join("; ", Errors)}";
}

Self-registration

public static class ModelBindingErrorHandlerRegister
{
    /// <summary>
    /// This method should be called after <b>AddControllers();</b> call.
    /// </summary>
    public static IServiceCollection AddModelBinderErrorHandler(this IServiceCollection services)
    {
        return AddModelBinderErrorHandler<ModelBindingErrorHandler>(services);
    }

    /// <summary>
    /// This method can be used to register a custom Model binder's error handler.
    /// </summary>
    /// <typeparam name="TImpl">A custom implementation of <see cref="IModelBindingErrorHandler"/></typeparam>
    public static IServiceCollection AddModelBinderErrorHandler<TImpl>(this IServiceCollection services)
        where TImpl : class, IModelBindingErrorHandler
    {
        services.AddSingleton<IModelBindingErrorHandler, TImpl>();

        var serviceProvider = services.BuildServiceProvider();
        var handler = serviceProvider.GetService<IModelBindingErrorHandler>();
        services.PostConfigure((ApiBehaviorOptions options) =>
            options.InvalidModelStateResponseFactory = handler.HandleInvalidModelState);

        return services;
    }
}

Usage

public partial class Startup
{
    private readonly IConfiguration Configuration;

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers()
            .AddNewtonsoftJson();
        
        //Omited for brevity
        
        services.AddModelBinderErrorHandler();
    }
    
    //Omited for brevity
}

Answered By – Peter Csala

Answer Checked By – Dawn Plyler (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *