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

How to selectively validate some data annotation attribute?

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

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

处理 ModelState 错误集合。

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

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

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

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

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

理想的解决方案

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

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

我认为您的问题有更精确的解决方案。假设您要提交给一种方法,我的意思是说您正在为部分提交和完整提交调用相同的方法。那么你应该像下面这样:

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

以上代码将获取所有可能出错的键并将它们从模型状态中删除。您需要将此行放在 if 条件中以确保它以部分形式提交运行。我还检查了错误只会出现在 [Required] 属性上(不知何故模型活页夹给予这个属性高优先级,即使你把它放在 after/below 任何其他属性)。让您再也不用担心模型更新了。

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

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

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

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

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

这将填充 return 一个 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);
    }
}

我的做法是添加条件检查注解属性,学习自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());
    }
}

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