如何有选择地验证一些数据注释属性?

时间:2015-06-17 12:41:41

标签: c# asp.net asp.net-mvc data-annotations model-binding

我的视图模型中有一些属性在保存时是可选的,但在提交时是必需的。总之,我们允许部分保存,但提交整个表单,我们确实希望确保所有必填字段都有值。

目前我能想到的唯一方法是:

操纵ModelState错误集合。

视图模型具有所有[Required]属性。如果请求是部分保存,则在输入控制器操作时ModelState.IsValid变为false。然后,我遍历所有ModelState(这是ICollection<KeyValuePair<string, ModelState>>)错误,并删除[Required]属性引发的所有错误。

但如果请求是提交整个表单,我不会干扰ModelState[Required]属性生效。

使用不同的视图模型进行部分保存并提交

这个更难看。一个视图模型将包含所有[Required]属性,由操作方法用于提交。但是对于部分保存,我将表单数据发布到使用相同视图模型的不同操作,而不使用所有[Required]属性。

显然,我最终会遇到很多重复的代码/视图模型。

理想的解决方案

我一直在考虑是否可以为这些必需的属性创建自定义数据注释属性[SubmitRequired]。并且以某种方式使验证在部分保存时忽略它,而不是在提交时忽略它。

仍然没有明确的线索。有人可以帮忙吗?感谢。

3 个答案:

答案 0 :(得分:2)

这是我在项目中使用的一种方法。

创建一个包含业务逻辑的ValidationService<T>,该逻辑将检查您的模型是否处于使用IsValidForSubmission方法提交的有效状态。

IsSubmitting属性添加到您在调用IsValidForSubmission方法之前检查的视图模型。

仅使用内置验证属性来检查无效数据,即字段长度等。

在不同的命名空间内创建一些自定义属性,这些属性将在某些情况下验证,即[RequiredIfSubmitting],然后在服务中使用反射来迭代每个属性上的属性并手动调用它们的IsValid方法(跳过)任何不在你的命名空间内的东西。)

这将填充并返回Dictionary<string, string>,可用于将ModelState填充回UI:

var validationErrors = _validationService.IsValidForSubmission(model);

if (validationErrors.Count > 0)
{
    foreach (var error in validationErrors)
    {
        ModelState.AddModelError(error.Key, error.Value);
    }
}

答案 1 :(得分:1)

我认为您的问题有更精确的解决方案。假设您提交了一种方法,我的意思是说您使用相同的方法进行部分和完全提交。那么你应该这样做:

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult YourMethod(ModelName model)
        {
          if(partialSave) // Check here whether it's a partial or full submit
          {
            ModelState.Remove("PropertyName");
            ModelState.Remove("PropertyName2");
            ModelState.Remove("PropertyName3");
          }

          if (ModelState.IsValid)
          {
          }
        }

这应该可以解决您的问题。如果您遇到任何麻烦,请告诉我。

修改

由于@SBirthare评论说在模型更新时添加或删除属性是不可行的,我在下面找到了适用于[Required]属性的解决方案。

 ModelState.Where(x => x.Value.Errors.Count > 0).Select(d => d.Key).ToList().ForEach(g => ModelState.Remove(g));

上面的代码将获取所有可能出错的密钥并将其从模型状态中删除。如果条件确实以部分形式提交,则需要将此行放在内部。我还检查了仅[Required]属性的错误(不知何故,模型绑定器为此属性赋予高优先级,即使您将其放在任何其他属性之后/之下)。因此,您不再需要担心模型更新。

答案 2 :(得分:0)

我的方法是添加条件检查注释属性,该属性是从foolproof中学习的。

使SaveMode成为视图模型的一部分。

将属性标记为可为空,以便SaveMode不是Finalize时,其值可选。

但添加自定义注记属性[FinalizeRequired]

[FinalizeRequired]
public int? SomeProperty { get; set; }

[FinalizeRequiredCollection]
public List<Item> Items { get; set; }

以下是属性的代码:

[AttributeUsage(AttributeTargets.Property)]
public abstract class FinalizeValidationAttribute : ValidationAttribute
{
    public const string DependentProperty = "SaveMode";

    protected abstract bool IsNotNull(object value);

    protected static SaveModeEnum GetSaveMode(ValidationContext validationContext)
    {
        var saveModeProperty = validationContext.ObjectType.GetProperty(DependentProperty);

        if (saveModeProperty == null) return SaveModeEnum.Save;

        return (SaveModeEnum) saveModeProperty.GetValue(validationContext.ObjectInstance);
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var saveMode = GetSaveMode(validationContext);

        if (saveMode != SaveModeEnum.SaveFinalize) return ValidationResult.Success;

        return (IsNotNull(value))
            ? ValidationResult.Success
            : new ValidationResult(string.Format("{0} is required when finalizing", validationContext.DisplayName));
    }
}

对于原始数据类型,请检查value!=null

[AttributeUsage(AttributeTargets.Property)]
public class FinalizeRequiredAttribute : FinalizeValidationAttribute
{
    protected override bool IsNotNull(object value)
    {
        return value != null;
    }
}

对于IEnumerable个收藏集,

[AttributeUsage(AttributeTargets.Property)]
public  class FinalizeRequiredCollectionAttribute : FinalizeValidationAttribute
{
    protected override bool IsNotNull(object value)
    {
        var enumerable = value as IEnumerable;
        return (enumerable != null && enumerable.GetEnumerator().MoveNext());
    }
}

这种方法通过从控制器中删除验证逻辑,最好地实现了关注点的分离。 Data Annotation属性应该处理这种工作,哪个控制器只需要检查!ModelState.IsValid。这在我的应用程序中特别有用,因为如果每个控制器中的ModelState检查不同,我将无法重构为基本控制器。