MVC ICollection <IFormFile> ValidationState始终设置为Skipped

Chr*_*ord 13 .net c# validation asp.net-core-mvc asp.net-core

作为ASP.NET Core MVC 1.0项目的一部分,我有一个带有ICollection<>属性的ViewModel .我需要验证此集合包含一个或多个项目.我的自定义验证属性未执行.

在我的实例中,它从multipart/form-data表单中保存多个文件附件.

我在ViewModel中使用自定义验证属性修饰了该属性:

[RequiredCollection]
public ICollection<IFormFile> Attachments { get; set; }
Run Code Online (Sandbox Code Playgroud)

下面是自定义属性类.它只是检查集合不是null并且元素大于零:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,在控制器中我确保POST请求中的ViewModel 有效,这应该触发验证:

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

如果我在ModelState.IsValid通话中休息,ModelState.ValuesAttachments属性的内容是:

Visual Studio本地窗口

  • 为什么RequiredCollectionAttribute.IsValid()方法中的断点不会受到影响?
  • 为什么ValidationStateget设置SkippedAttachments属性?

-

编辑1:

MethodViewModel定义,按要求:

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

-

编辑2:

以下是按要求修剪的值actionContext.ModelState(以JSON格式导出).这是在进入全局操作过滤器时遇到断点的状态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
            } 
        ]
    } 
}
Run Code Online (Sandbox Code Playgroud)

-

编辑3:

视图的剃刀语法用于呈现Attachments输入字段.

<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>
Run Code Online (Sandbox Code Playgroud)

Wil*_*Ray 7

如果发现IFormFile或者集合IFormFile不是null,MVC似乎会抑制进一步的验证.

如果您查看FormFileModelBinder.cs代码,可以在此处查看问题.如果绑定器能够从上面的if/elseif/else子句获得非null结果,则禁止验证.

在测试中,我使用如下代码创建了一个视图模型:

[ThisAttriuteAlwaysReturnsAValidationError]
public IFormFile Attachment { get;set; }
Run Code Online (Sandbox Code Playgroud)

当我实际上传文件到这个例子时,它上面的永远错误的属性永远不会被调用.

由于这是来自MVC本身,我认为你最好的选择是实现IValidateableObject接口.

public class YourViewModel : IValidatableObject
{
    public ICollection<IFormFile> Attachments { get;set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var numAttachments = Attachments?.Count() ?? 0;
        if (numAttachments == 0)
        {
            yield return new ValidationResult(
                "You must attached at least one file.",
                new string[] { nameof(Attachments) });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

仍然会调用此方法,因为它与任何单个属性都没有关联,因此MVC不会像您的属性那样将其抑制.

如果您必须在多个地方执行此操作,则可以创建一个扩展方法来提供帮助.

public static bool IsNullOrEmpty<T>(this IEnumerable<T> collection) =>
        collection == null || !collection.GetEnumerator().MoveNext();
Run Code Online (Sandbox Code Playgroud)

更新

这已作为错误提交,应在1.0.0 RTM中修复.