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

时间:2016-03-14 14:14:25

标签: c# .net validation asp.net-core asp.net-core-mvc

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

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

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

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

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

如果我在ModelState.IsValid来电中断,则ModelState.Values属性的Attachments内容为:

Visual Studio Locals Window

问题

  • 为什么RequiredCollectionAttribute.IsValid()方法中的断点不会被击中?
  • 为什么ValidationState属性的Skipped设置为Attachments

-

编辑1

MethodViewModel定义,按要求:

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

-

编辑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
            } 
        ]
    } 
}

-

编辑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>

2 个答案:

答案 0 :(得分:7)

如果.ui-dialogIFormFile的集合被发现不为空,则MVC似乎禁止进一步验证。

如果您查看IFormFile代码,you can see the issue right here。如果绑定器能够从上面的if / elseif / else子句获得非null结果,则禁止验证。

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

FormFileModelBinder.cs

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

由于这是来自MVC本身,我认为最好的办法是实现[ThisAttriuteAlwaysReturnsAValidationError] public IFormFile Attachment { get;set; } 接口。

IValidateableObject

仍然会调用此方法,因为它与任何单个属性都没有关联,因此MVC不会像您的属性那样禁止它。

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

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

更新

这是filed as a bug,应该在1.0.0 RTM中修复。

答案 1 :(得分:1)

部分答案(仅针对代码共享问题)

试试这个:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class RequiredCollectionAttribute : ValidationAttribute
{

    public override bool IsValid(object value)
    {
        ErrorMessage = "You must provide at least one.";
        var collection = value as ICollection;

        return collection != null || collection.Count > 0;
    }
}

另外,尝试添加过滤器。

GlobalConfiguration.Configuration.Filters.Add(new RequestValidationFilter());

并编写过滤器本身:

public class RequestValidationFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (actionContext.ModelState.IsValid == false)
        {
            var errors = actionContext.ModelState
                                      .Values
                                      .SelectMany(m => m.Errors
                                                        .Select(e => e.ErrorMessage));

            actionContext.Response = actionContext.Request.CreateErrorResponse(
                HttpStatusCode.BadRequest, actionContext.ModelState);

            actionContext.Response.ReasonPhrase = string.Join("\n", errors);
        }
    }
}

仅供我们检查是否在过滤器内触发了断点。