1

I'm looking for a way to add error code alongside the error message to ModelState. for example

ModelState.AddModelError("ErrorKey", new { Code = 4001, Message = "Some error message" });

For some bad requests client should do an action and comparing error message is not an ideal solution for making a decision. ModelState.AddModelError method only accepts two parameters, an error key and a message. Is there a way to achieve this or something similar?

Mohsen Esmailpour
  • 11,224
  • 3
  • 45
  • 66

2 Answers2

0

No, there is not a way to achieve what you are looking for, in your code when you're trying to do something like this:

return BadRequest(ModelState); 

You’ll receive a 400 bad request response back with message you've already added (as you can see, the error code has been presented already here). So, there is neither a usage nor a way of adding the Error Code in your case.

Salah Akbari
  • 39,330
  • 10
  • 79
  • 109
0

I found a way to add the error code to ValidationProblemDetails:


public class CustomValidationProblemDetails : ValidationProblemDetails
{
    public CustomValidationProblemDetails()
    {
    }

    [JsonPropertyName("errors")]
    public new IEnumerable<ValidationError> Errors { get; } = new List<ValidationError>();
}

ValidationProblemDetails has an Error property that is IDictionary<string, string[]> and replace this property with our version to add code error.

public class ValidationError
{
    public int Code { get; set; }

    public string Message { get; set; }
}

Constructor of ValidationProblemDetails accepts ModelStateDictionary and need to convert it to list of ValidationError:

public CustomValidationProblemDetails(IEnumerable<ValidationError> errors)
{
    Errors = errors;
}

public CustomValidationProblemDetails(ModelStateDictionary modelState)
{
    Errors = ConvertModelStateErrorsToValidationErrors(modelState);
}

private List<ValidationError> ConvertModelStateErrorsToValidationErrors(ModelStateDictionary modelStateDictionary)
{
    List<ValidationError> validationErrors = new();

    foreach (var keyModelStatePair in modelStateDictionary)
    {
        var errors = keyModelStatePair.Value.Errors;
        switch (errors.Count)
        {
            case 0:
                continue;

            case 1:
                validationErrors.Add(new ValidationError { Code = 100, Message = errors[0].ErrorMessage });
                break;

            default:
                var errorMessage = string.Join(Environment.NewLine, errors.Select(e => e.ErrorMessage));
                validationErrors.Add(new ValidationError { Message = errorMessage });
                break;
        }
    }

    return validationErrors;
}

Create custom ProblemDetailsFactory to create CustomValidationProblemDetails when we want to return bad request response:

public class CustomProblemDetailsFactory : ProblemDetailsFactory
{
    public override ProblemDetails CreateProblemDetails(HttpContext httpContext, int? statusCode = null, string title = null,
        string type = null, string detail = null, string instance = null)
    {
        var problemDetails = new ProblemDetails
        {
            Status = statusCode,
            Title = title,
            Type = type,
            Detail = detail,
            Instance = instance,
        };

        return problemDetails;
    }

    public override ValidationProblemDetails CreateValidationProblemDetails(HttpContext httpContext,
        ModelStateDictionary modelStateDictionary, int? statusCode = null, string title = null, string type = null,
        string detail = null, string instance = null)
    {
        statusCode ??= 400;
        type ??= "https://tools.ietf.org/html/rfc7231#section-6.5.1";
        instance ??= httpContext.Request.Path;

        var problemDetails = new CustomValidationProblemDetails(modelStateDictionary)
        {
            Status = statusCode,
            Type = type,
            Instance = instance
        };

        if (title != null)
        {
            // For validation problem details, don't overwrite the default title with null.
            problemDetails.Title = title;
        }

        var traceId = Activity.Current?.Id ?? httpContext?.TraceIdentifier;
        if (traceId != null)
        {
            problemDetails.Extensions["traceId"] = traceId;
        }

        return problemDetails;
    }
}

And at the end register the factory:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddTransient<ProblemDetailsFactory, CustomProblemDetailsFactory>();
}

Read the Extending ProblemDetails - Add error code to ValidationProblemDetails for more detail.

Mohsen Esmailpour
  • 11,224
  • 3
  • 45
  • 66