5

I am working on an ASP.NET Core 6 Web API project. We use Fluent Validation. How can I return custom response. Please advice.

I would like to send custom response on different validation error such as page, limit, date etc

This is the response I get by default:

  {
       "type": "",
       "title": "One or more validation errors occurred.",
       "status": 400,
       "traceId": "00-097c689850af328c49705cea77fc6fbd-0c7defe165d9693f-00",
       "errors": {
         "Page": [
           "Page value should be greater than 0"
         ],
         "Limit": [
           "Limit value should be greater than 0"
         ]
       }
}

And this is the response I would like to get:

{
  "traceId": "e45b3398-a2a5-43eb-8851-e45de1184021",
  "timestamp": "2021-06-28T00:32:06.548Z",
  "errors": [
    {
      "errorCode": "Contract Violation",
      "message": "Page should be greater than 0",
      "priority": "MEDIUM",
      "properties": {
        "name": "string",
        "value": "string"
      }
    }
  ]
}

This is my code:

public async Task<IActionResult> Get([FromQuery] DataParameter input) 
{

}

public class DataParameter 
{
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public int Page { get; set; }
    public int Limit { get; set; }
}

public class QueryParameterValidator : AbstractValidator<QueryParameter>
{
    public QueryParameterValidator()
    {
        RuleFor(model => model.Page)
            .GreaterThan(0)
            .WithMessage("Page value should be greater than 0");
        RuleFor(model => model.Limit)
            .GreaterThan(0)
            .WithMessage("Limit value should be greater than 0");
    
        RuleFor(model => model.StartDate)
            .Matches(@"^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$")
            .WithMessage("Transaction from date should be in the pattern of YYYY-MM-DD");
        RuleFor(model => model.EndDate)
            .Matches(@"^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$")
            .WithMessage("Transaction to date should be in the pattern of YYYY-MM-DD");
    }
}
2
  • Hi @itmannz, The response contains two error but your expected response only has one error, do you only want to display one error? I suggest you can custom a class which inherits from ValidationProblemDetails. Commented Apr 21, 2022 at 6:31
  • Hi Rena, Thanks. May I have any example please. Commented Apr 21, 2022 at 12:54

1 Answer 1

11

Here is a whole working demo you could follow:

Model:

public class ErrorDetail
{
    public string traceId { get; set; }
    public DateTime timestamp { get; set; }
    public List<Error> errors { get; set; } = new List<Error>();
}

public class Error
{
    public string errorCode { get; set; }
    public string message { get; set; }
    public string priority { get; set; }
    public Properties properties { get; set; }=new Properties();
}

public class Properties
{
    public string name { get; set; }
    public string value { get; set; }
}

Static class:

public static class CustomProblemDetails
{
    public static ErrorDetail ErrorDetail { get; set; } =new ErrorDetail();
    public static IActionResult MakeValidationResponse(ActionContext context)
    {
        var problemDetails = new ValidationProblemDetails(context.ModelState)
        {
            Status = StatusCodes.Status400BadRequest,
        };
        foreach (var keyModelStatePair in context.ModelState)
        {
            var errors = keyModelStatePair.Value.Errors;
            if (errors != null && errors.Count > 0)
            {
                if (errors.Count == 1)
                {
                    var errorMessage = GetErrorMessage(errors[0]);
                    ErrorDetail.errors.Add(new Error()
                    {
                        errorCode= "Contract Violation",
                        message = errorMessage,
                        priority= "MEDIUM",
                        properties= new Properties() { 
                            name = "string", 
                            value = "string" 
                        }
                    });
                }
                else
                {
                    var errorMessages = new string[errors.Count];
                    for (var i = 0; i < errors.Count; i++)
                    {
                        errorMessages[i] = GetErrorMessage(errors[i]);
                        ErrorDetail.errors.Add(new Error()
                        {
                            errorCode = "Contract Violation",
                            message = errorMessages[i],
                            priority = "MEDIUM"
                        });
                    }
                }
            }
        }
        ErrorDetail.traceId = context.HttpContext.TraceIdentifier;
        ErrorDetail.timestamp = DateTime.Now;

        var result = new BadRequestObjectResult(ErrorDetail);

        result.ContentTypes.Add("application/problem+json");

        return result;
    }
    static string GetErrorMessage(ModelError error)
    {
        return string.IsNullOrEmpty(error.ErrorMessage) ?
        "The input was not valid." :
        error.ErrorMessage;
    }
}

Register:

builder.Services.AddControllers().AddFluentValidation().ConfigureApiBehaviorOptions(options =>
{
    options.InvalidModelStateResponseFactory = CustomProblemDetails.MakeValidationResponse;
}); 

Result:

enter image description here

Sign up to request clarification or add additional context in comments.

1 Comment

Hi @Rena, Thanks for help. why static class CustomProblemDetails and static, ErrorDetail, IActionResult Also, If I click the execute button in swagger it add to the "errors" array.

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.