2
votes

Let's say that I have simple model with required attribute above property.

public class User
{
    [Required]
    string Name {get;set;}
    string Surname {get;set;}
}

When I POST/PUT only one instance of User and Name is empty it works pretty well. ModelState is not valid and contains error.

When I POST/PUT collection of objects User and in some of them Name is empty then ModelState is valid and it does not contain any validation errors.

Could you tell me what is wrong with it and why it concerns only collections? I noticed same behaviour when I have one object with relation one-many. Then collection within this object also is not validated by ModelState.

I don't want to validate required fields manually, it should work automatically.

1
Validation is property-level, there is a workaround that you can put the collection into a model as a property.Takahiro

1 Answers

-1
votes

You need to create a ActionFilter

public class ModelStateValidActionFilter : IAsyncActionFilter
{
    public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Validate ICollection
        if (context.ActionArguments.Count == 1 && context.ActionArguments.First().Value.GetType().IsListType())
        {
            foreach (var arg in (IList)context.ActionArguments.First().Value  )
            {
                var parameters = arg.GetType().GetProperties();
                foreach (var parameter in parameters)
                {
                    var argument = context.ActionArguments.GetOrDefault(parameter.Name);
                    EvaluateValidationAttributes(parameter, argument, context.ModelState);
                }
            }
        }

        if (context.ModelState.IsValid)
        {
            return next();
        }
        context.Result = new BadRequestObjectResult(context.ModelState);
        return Task.CompletedTask;
    }

    private void EvaluateValidationAttributes(PropertyInfo parameter, object argument, ModelStateDictionary modelState)
    {
        var validationAttributes = parameter.CustomAttributes;
        foreach (var attributeData in validationAttributes)
        {
            var attributeInstance = parameter.GetCustomAttribute(attributeData.AttributeType);
            var validationAttribute = attributeInstance as ValidationAttribute;
            if (validationAttribute != null)
            {
                var isValid = validationAttribute.IsValid(argument);
                if (!isValid)
                {
                    modelState.AddModelError(parameter.Name, validationAttribute.FormatErrorMessage(parameter.Name));
                }
            }
        }
    }

and add it into your MVC options

services.AddMvc()
    .AddMvcOptions(opts =>
    {
        opts.Filters.Add(new ModelStateValidActionFilter());
    }