作为 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
内容为:
RequiredCollectionAttribute.IsValid()
方法中的断点不会被击中? ValidationState
属性的Skipped
设置为Attachments
? -
MethodViewModel定义,按要求:
public class MethodViewModel
{
...
[Display(Name = "Attachments")]
[RequiredCollection(ErrorMessage = "You must attached at least one file.")]
public ICollection<IFormFile> Attachments { get; set; }
...
}
-
以下是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
}
]
}
}
-
用于呈现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>
答案 0 :(得分:7)
如果.ui-dialog
或IFormFile
的集合被发现不为空,则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);
}
}
}
仅供我们检查是否在过滤器内触发了断点。