MVC ICollection ValidationState 始终设置为 Skipped

2024-01-07

作为一个ASP.NET Core MVC 1.0项目,我有一个 ViewModelICollection<>财产。我需要验证该集合是否包含一项或多项。我的自定义验证属性没有被执行。

在我的实例中,它包含来自multipart/form-data form.

我用自定义验证属性装饰了 ViewModel 中的属性:

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

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

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);
    }
}

最后,在控制器中,我确保 ViewModel 位于POST请求有效,其中should触发验证:

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

如果我打破ModelState.IsValid调用,内容ModelState.Values为了Attachments属性是:

Question

  • 为什么我的断点不在RequiredCollectionAttribute.IsValid()方法曾经受到打击吗?
  • 为什么ValidationState设置为Skipped为了Attachments财产?

--

Edit 1:

MethodViewModel 定义,根据要求:

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

--

Edit 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
            } 
        ]
    } 
}

--

Edit 3:

视图的 razor 语法用于渲染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>

看起来 MVC 正在抑制进一步的验证,如果IFormFile或集合IFormFile发现不为空。

如果你看一下FormFileModelBinder.cs code, 你可以在这里看到这个问题 https://github.com/aspnet/Mvc/blob/373361675c74af3376bcfa59cb881519e79081c5/src/Microsoft.AspNet.Mvc.Core/ModelBinding/FormFileModelBinder.cs#L70。如果绑定器能够从上面的 if/elseif/else 子句获取非空结果,则它会抑制验证。

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

[ThisAttriuteAlwaysReturnsAValidationError]
public IFormFile Attachment { get;set; }

当我实际将文件上传到此示例时,它上面的always-erroring 属性永远不会被调用。

由于这是来自 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) });
        }
    }
}

该方法仍将被调用,因为它不与任何单独的属性关联,因此不会像您的属性那样被 MVC 抑制。

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

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

Update

这已经是归档为错误 https://github.com/aspnet/Mvc/issues/4329并且应该在 1.0.0 RTM 中修复。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MVC ICollection ValidationState 始终设置为 Skipped 的相关文章

随机推荐