C# 如何有选择地验证某些数据注释属性?
我的视图模型中有一些属性在保存时是可选的,但在提交时是必需的。总之,我们允许部分保存,但整个表单已提交,我们确实希望确保所有必填字段都有值 目前我能想到的唯一办法是: 操作ModelState错误集合。 视图模型具有所有C# 如何有选择地验证某些数据注释属性?,c#,asp.net,asp.net-mvc,data-annotations,model-binding,C#,Asp.net,Asp.net Mvc,Data Annotations,Model Binding,我的视图模型中有一些属性在保存时是可选的,但在提交时是必需的。总之,我们允许部分保存,但整个表单已提交,我们确实希望确保所有必填字段都有值 目前我能想到的唯一办法是: 操作ModelState错误集合。 视图模型具有所有[必需]属性。如果请求是部分保存,则进入控制器操作时,ModelState.IsValid将变为false。然后我运行所有ModelState(这是一个ICollection)错误,并删除[必需]属性引发的所有错误 但是,如果请求提交整个表单,我不会干扰ModelState,并且
[必需]
属性。如果请求是部分保存,则进入控制器操作时,ModelState.IsValid
将变为false
。然后我运行所有ModelState
(这是一个ICollection
)错误,并删除[必需]
属性引发的所有错误
但是,如果请求提交整个表单,我不会干扰ModelState
,并且[必需]
属性生效
使用不同的视图模型进行部分保存和提交
这个更难看。一个视图模型将包含操作方法用于提交的所有[必需]
属性。但是为了部分保存,我将表单数据发布到不同的操作,这些操作使用相同的视图模型,没有所有的[必需]
属性
显然,我最终会得到许多重复的代码/视图模型
理想溶液
我一直在考虑是否可以为这些必需的属性创建自定义数据注释属性[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评论说,在模型更新时添加或删除属性是不可行的,我发现下面的解决方案应该适用于[必需]
属性
ModelState.Where(x => x.Value.Errors.Count > 0).Select(d => d.Key).ToList().ForEach(g => ModelState.Remove(g));
上面的代码将获取所有可能出错的密钥,并将它们从模型状态中删除。您需要将此行放在if条件中,以确保它以部分提交形式运行。我还检查了错误只会出现在
[必需]
属性上(不知何故,model binder赋予此属性高优先级,即使您将其放在任何其他属性之后/之下)。因此,您不再需要担心模型更新。这是我在项目中使用的一种方法
创建一个包含业务逻辑的ValidationService
,该业务逻辑将检查您的模型是否处于有效状态,以便使用IsValidForSubmission
方法提交
在调用IsValidForSubmission
方法之前,向查看模型添加一个IsSubmitting
属性
仅使用内置验证属性检查无效数据,如字段长度等
在不同的命名空间中创建一些自定义属性,这些属性将在某些情况下进行验证,例如[RequiredIfSubmitting]
,然后在服务中使用反射来迭代每个属性上的属性,并手动调用其IsValid
方法(跳过命名空间之外的任何属性)
这将填充并返回一个字典
,该字典可用于填充ModelState
,返回到用户界面:
var validationErrors = _validationService.IsValidForSubmission(model);
if (validationErrors.Count > 0)
{
foreach (var error in validationErrors)
{
ModelState.AddModelError(error.Key, error.Value);
}
}
我的方法是添加条件检查注释属性,这是从中学习到的 使
SaveMode
成为视图模型的一部分
将属性标记为空,以便在SaveMode
不是Finalize
时,其值是可选的
但添加自定义注释属性[FinalizereRequired]
:
[FinalizeRequired]
public int? SomeProperty { get; set; }
[FinalizeRequiredCollection]
public List<Item> Items { get; set; }
对于基本数据类型,请选中value=空
:
[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
检查不同,我将无法重构为基本控制器。您可以使用[RequiredIfTrue]
或类似属性(基于视图模型中附加的bool SubmitRequired
属性。如果该值为false
,则不会对用[RequiredIfTrue“SubmitRequired”]修饰的属性执行任何验证)
,否则属性将被验证。谢谢,Stephen。该软件包是beta版,最后一次更新是在2012年。但此RequiredIf
确实指向了正确的方向。非常感谢。让我附上我将使用的代码的链接:它们完全相同:)没有必要使用万无一失的库。RequiredIf是一个非常常见的属性,您可以找到它的一些代码或。这实际上是我提出的第一个解决方案-在控制器操作中操纵ModelState
。但是感谢您的输入。您提出的解决方案没有成本效益,亲爱的。您建议循环所有属性。虽然我建议删除那些必需的属性,但不是在部分发布时。虽然这可能是一个简单的解决方案,在您没有属性的情况下效果很好,但它并不优雅。无意冒犯。它只是打破了OCP,即在模型中添加了一个新属性,您必须来到这里并添加到列表中。
[AttributeUsage(AttributeTargets.Property)]
public class FinalizeRequiredCollectionAttribute : FinalizeValidationAttribute
{
protected override bool IsNotNull(object value)
{
var enumerable = value as IEnumerable;
return (enumerable != null && enumerable.GetEnumerator().MoveNext());
}
}