我的视图模型中有一些属性在保存时是可选的,但在提交时是必需的。总之,我们允许部分保存,但提交整个表单,我们确实希望确保所有必填字段都有值。
目前我能想到的唯一方法是:
视图模型具有所有[Required]
属性。如果请求是部分保存,则在输入控制器操作时ModelState.IsValid
变为false
。然后,我遍历所有ModelState
(这是ICollection<KeyValuePair<string, ModelState>>
)错误,并删除[Required]
属性引发的所有错误。
但如果请求是提交整个表单,我不会干扰ModelState
和[Required]
属性生效。
这个更难看。一个视图模型将包含所有[Required]
属性,由操作方法用于提交。但是对于部分保存,我将表单数据发布到使用相同视图模型的不同操作,而不使用所有[Required]
属性。
显然,我最终会遇到很多重复的代码/视图模型。
我一直在考虑是否可以为这些必需的属性创建自定义数据注释属性[SubmitRequired]
。并且以某种方式使验证在部分保存时忽略它,而不是在提交时忽略它。
仍然没有明确的线索。有人可以帮忙吗?感谢。
答案 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
检查不同,我将无法重构为基本控制器。