Search code examples
c#asp.net-core-3.1fluentvalidationmodel-validation

.NET Core 3.1 custom model validation with fluentvalidation


Im trying to learn .net 3.1 by building a small test webapi and currently my objective is to validate dtos with fluentvalidation and in case it fails, present a custom json to the caller. The problems i have found and cant get over are two;

  • i cant seem to get the messages i write via fluentvalidation (they always are the - i assume .net core default ones)
  • i cant seem to modify the object type that is json-ified and then output to the caller.

My code is as follows:

1. The Controller

    [ApiController]
[Route("[controller]")]
public class AdminController : ControllerBase
{

    [HttpPost]
    [ProducesResponseType(StatusCodes.Status409Conflict)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status202Accepted)]
    public async Task<IActionResult> RegisterAccount(NewAccountInput dto)
    {

        return Ok();
    }        
}

2. The Dto and the custom validator

 public class NewAccountInput
    {
        public string Username { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public AccountType Type { get; set; }
    }

    public class NewAccountInputValidator : AbstractValidator<NewAccountInput>
    {
        public NewAccountInputValidator()
        {
            RuleFor(o => o.Email).NotNull().NotEmpty().WithMessage("Email vazio");
            RuleFor(o => o.Username).NotNull().NotEmpty().WithMessage("Username vazio");
        }
    }

3. The Filter im using for validation

    public class ApiValidationFilter : ActionFilterAttribute
        {
            public override  void OnActionExecuting(ActionExecutingContext context)
            {
                if (!context.ModelState.IsValid)
                {
//the only output i want are the error descriptions, nothing else
                    var data = context.ModelState
                        .Values
                        .SelectMany(v => v.Errors.Select(b => b.ErrorMessage))
                        .ToList();
                    
                    context.Result = new JsonResult(data) { StatusCode = 400 };
                }
                //base.OnActionExecuting(context);
            }
        }

finally, my configureservices

 public void ConfigureServices(IServiceCollection services)
        {
            services
                //tried both lines and it doesnt seem to work either way
                .AddScoped<ApiValidationFilter>()
                .AddControllers(//config=>
                                //config.Filters.Add(new ApiValidationFilter())                
                )
                .AddFluentValidation(fv => {
                    fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;//i was hoping this did the trick
                    fv.RegisterValidatorsFromAssemblyContaining<NewAccountInputValidator>();
                    
                });

        }

Now, trying this with postman i get the result

img

which highlights both issues im having atm

This was done with asp.net core 3.15 and visualstudio 16.6.3


Solution

  • The message you are seeing is in fact coming from FluentValidation - see the source.

    The reason you aren't seeing the custom message you are providing is that FluentValidation will show the validation message from the first validator that fails in the chain, in this case NotNull.

    This question gives some options for specifying a single custom validation message for an entire chain of validators.

    In this case the Action Filter you describe is never being hit, as the validation is failing first. To prevent this you can use:

    services.Configure<ApiBehaviorOptions>(options =>
    {
        options.SuppressModelStateInvalidFilter = true;
    });
    

    which will stop the automatic return of BadRequest for an invalid model. This question provides some alternative solutions, including configuring an InvalidModelStateResponseFactory to do what you require.