MVC ICollection<IFormFile> ValidationState always set to Skipped

Viewed 3009

As part of an ASP.NET Core MVC 1.0 project, I have a ViewModel with an ICollection<> property. I need to validate that this collection contains one or more items. My custom validation attribute doesn't get executed.

In my instance it holds multiple file attachments from a multipart/form-data form.

I have decorated the property in the ViewModel with a custom validation attribute:

[RequiredCollection]
public ICollection<IFormFile> Attachments { get; set; }

Below is the custom attribute class. It simply checks the collection is not null and has greater than zero elements:

public class RequiredCollectionAttribute : ValidationAttribute
{
    protected const string DefaultErrorMessageFormatString = "You must provide at least one.";

    public RequiredCollectionAttribute() : base(DefaultErrorMessageFormatString) { }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var collection = (ICollection) value;

        return collection == null || collection.Count > 0
            ? ValidationResult.Success
            : new ValidationResult(ErrorMessageString);
    }
}

And finally, in the controller I am ensuring the ViewModel in the POST request is valid, which should trigger the validation:

[HttpPost]
public async Task<IActionResult> Method(MethodViewModel viewModel)
{
    if (!ModelState.IsValid)
        return View(viewModel);
    ...
}

If I break at the ModelState.IsValid call, the contents of ModelState.Values for the Attachments property is:

Visual Studio Locals Window

Question

  • Why doesn't my breakpoint within the RequiredCollectionAttribute.IsValid() method ever get hit?
  • Why does the ValidationState get set to Skipped for the Attachments property?

--

Edit 1:

MethodViewModel definition, as requested:

public class MethodViewModel
{
    ...
    [Display(Name = "Attachments")]
    [RequiredCollection(ErrorMessage = "You must attached at least one file.")]
    public ICollection<IFormFile> Attachments { get; set; }
    ...
}

--

Edit 2:

Below is the trimmed value of actionContext.ModelState (exported in JSON), as requested. This is the state when a breakpoint is hit on entry to a global action filter, OnActionExecuting():

{
    "Count": 19,
    "ErrorCount": 0,
    "HasReachedMaxErrors": false,
    "IsReadOnly": false,
    "IsValid": true,
    "Keys": 
    [
        "Attachments"
    ], 
    "MaxAllowedErrors": 200,
    "ValidationState": Valid,
    "Values": 
    [
        {
            "AttemptedValue": null,
            {
            }, 
            "RawValue": null,
            "ValidationState": Microsoft.AspNet.Mvc.ModelBinding.ModelValidationState.Skipped
        }
    ], 
    {
        [
            "Key": "Attachments",
            {
                "AttemptedValue": null,
                "RawValue": null,
                "ValidationState": Microsoft.AspNet.Mvc.ModelBinding.ModelValidationState.Skipped
            }, 
            "key": "Attachments",
            {
                "AttemptedValue": null,
                "RawValue": null,
                "ValidationState": Microsoft.AspNet.Mvc.ModelBinding.ModelValidationState.Skipped
            } 
        ]
    } 
}

--

Edit 3:

The view's razor syntax to render the Attachments input field.

<form role="form" asp-controller="Controller" asp-action="Method" method="post" enctype="multipart/form-data">
    ...
    <div class="form-group">
        <label asp-for="Attachments" class="control-label col-xs-3 col-sm-2"></label>
        <div class="col-xs-9 col-sm-10">
            <input asp-for="Attachments" class="form-control" multiple required>
            <span asp-validation-for="Attachments" class="text-danger"></span>
        </div>
    </div>
    ...
</form>
2 Answers
Related