MVC ICollection ValidationState始终设置为Skipped
作为ASP.NET Core MVC 1.0项目的一部分,我有一个带有ICollection
属性的ViewModel。 我需要validation此集合包含一个或多个项目。 我的自定义validation属性未执行。
在我的实例中,它从multipart/form-data
表单中保存多个文件附件。
我在ViewModel中使用自定义validation属性修饰了该属性:
[RequiredCollection] public ICollection Attachments { get; set; }
下面是自定义属性类。 它只是检查集合不是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); } }
最后,在控制器中,我确保POST
请求中的ViewModel有效,这应该触发validation:
[HttpPost] public async Task Method(MethodViewModel viewModel) { if (!ModelState.IsValid) return View(viewModel); ... }
如果我在ModelState.IsValid
调用中断,则Attachments
属性的ModelState.Values
的内容为:
题
- 为什么我的断点在
RequiredCollectionAttribute.IsValid()
方法中没有被击中? - 为什么
ValidationState
被设置为Skipped
forAttachments
属性?
–
编辑1 :
MethodViewModel定义,按要求:
public class MethodViewModel { ... [Display(Name = "Attachments")] [RequiredCollection(ErrorMessage = "You must attached at least one file.")] public ICollection Attachments { get; set; } ... }
–
编辑2 :
下面是actionContext.ModelState
的修剪值(按JSON导出),如下所示。 这是在进入全局操作filterOnActionExecuting()
时遇到断点的状态:
{ "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
输入字段。
如果发现IFormFile
或IFormFile
的集合不为null,MVC似乎会抑制进一步的validation。
如果您查看FormFileModelBinder.cs
代码, 可以在此处查看问题 。 如果绑定器能够从上面的if / elseif / else子句获得非null结果,则禁止validation。
在测试中,我使用如下代码创建了一个视图模型:
[ThisAttriuteAlwaysReturnsAValidationError] public IFormFile Attachment { get;set; }
当我实际上传文件到这个例子时,它上面的永远错误的属性永远不会被调用。
由于这是来自MVC本身,我认为你最好的选择是实现IValidateableObject
接口。
public class YourViewModel : IValidatableObject { public ICollection Attachments { get;set; } public IEnumerable 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(this IEnumerable collection) => collection == null || !collection.GetEnumerator().MoveNext();
更新
这已作为错误提交 ,应在1.0.0 RTM中修复。
部分答案 (仅用于代码共享)
试试这个:
[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; } }
另外,尝试添加filter。
GlobalConfiguration.Configuration.Filters.Add(new RequestValidationFilter());
并编写filter本身:
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); } } }
只是为了我们检查filter内是否触发了断点。