在完成MVC 2 NerdDinner tutorial工作时,我发现可以将某些业务逻辑(必填字段,最大长度等)添加到模型的各个属性中。
如何添加更复杂的验证规则?例如,要求填充两个属性中的一个?
示例[QuantumMechanics / Models / Particle.cs]:
namespace QuantumMechanics.Models
{
[MetadataType(typeof(Particle_Validation))]
public partial class Particle {
}
public class Particle_Validation
{
// Mass is required; easy enough.
[Required(ErrorMessage="Mass is required.")]
public Mass double {get; set; }
// How do I require exactly one or the other?
public Position double {get; set; }
public Momentum double {get; set; }
}
}
答案 0 :(得分:0)
在 MVC2 中,一旦将值发布到控制器,您就会执行检查。
[HttpPost]
public ActionResult Add(Particle particleToAdd)
{
ValidateModel(particleToAdd);
if(ModelState.IsValid)
{
// Add particle
return RedirectToAction(...) // etc
}
// Return the view with our errors
return View(particleToAdd);
}
// This validate method can be invoked from your Add and Edit actions
private void ValidateModel(Particle particleToAddOrUpdate)
{
if(particleToAddOrUpdate.Position == null && particleToAddOrUpdate.Momentum == null)
{
ModelState.AddModelError("", "You must supply a value for either Position or Momentum");
}
}
如果你喜欢(位置或动量),你可以将错误添加到其中一个属性中,但我刚刚将它添加到将出现在验证摘要中的通用错误列表中。
<%: Html.ValidationSummary() %>
在 MVC3 中,有一个接口IValidateObject
可以稍微抽象出这个问题。它允许您执行每个类指定的检查,即您的粒子对象可以在绑定时检查自己的属性,而不必在Add
方法中编写检查。
这是example的用法。
不幸的是, MVC2 中没有任何内容可以让您验证绑定类的多个属性,例如 MVC3 中的IValidateObject
接口。您只需确保在调用Add
或Edit
操作时调用其他验证方法。
答案 1 :(得分:0)
听起来你的目标是“条件要求”规则,即根据另一个属性的值标记为必需的属性。仅使用ValidationAttribute是不可能的,因为该属性的范围仅限于它所装饰的属性。您需要实现匹配的DataAnnotationsModelValidator
以具有更宽的范围(以及ModelClientValidationRule
与关联的客户端javascript,如果您还希望它还验证客户端)。
因此,您的ConditionalRequiredAttribute看起来像:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class ConditionalRequiredAttribute : ValidationAttribute
{
public ConditionalRequiredAttribute(string triggerProperty, object triggerValue)
{
TriggerProperty = triggerProperty;
TriggerValue = triggerValue;
}
/// <summary>
/// Checks that the value of the decorated member is not null/empty if the TriggerProperty's value is equal to TriggerValue.
/// </summary>
/// <param name="value">The data field value to validate.</param>
/// <returns>true if validation is successful; otherwise, false.</returns>
public override bool IsValid(object value)
{
if (value == null)
return false;
if (value is double)
{
return !((double)value).Equals(0);
}
string s = value as string;
if (s != null)
return s.Length > 0;
return true;
}
/// <summary>
/// The name of the property whose value will be checked to trigger the required field
/// </summary>
public string TriggerProperty { get; set; }
/// <summary>
/// The expected value of the trigger property that will trigger the required field
/// </summary>
public object TriggerValue { get; set; }
}
这实际上与标准Required
属性非常相似 - “特殊酱”实际上是验证器,您在某些情况下仅用于调用IsValid
方法(即{{1 }} == TriggerProperty.Value
)。验证器看起来像:
TriggerValue
最后,您需要向提供商注册 public class ConditionalRequiredValidator : DataAnnotationsModelValidator<ConditionalRequiredAttribute>
{
public ConditionalRequiredValidator(ModelMetadata metadata, ControllerContext context,
ConditionalRequiredAttribute attribute)
: base(metadata, context, attribute)
{
}
/// <summary>
/// Override the default validate method to only execute if the TriggerProperty's value is equal to TriggerValue
/// </summary>
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// Does the specified property exist in the metadata?
PropertyInfo triggerProperty = Metadata.ContainerType.GetProperty(Attribute.TriggerProperty);
if (triggerProperty != null)
{
object actualValue = triggerProperty.GetValue(container, null);
if (actualValue != null)
{
if (Attribute.TriggerValue.Equals(actualValue))
{
// Run IsValid for the property if the actual value matches the expected value
foreach (ModelValidationResult result in base.Validate(container))
{
yield return result;
}
}
}
}
}
}
,以确保在使用ConditionalRequiredValidator
时框架更喜欢它。为此,请在Global.asax.cs的ConditionalRequiredAttribute
方法中添加以下行:
Application_Start()
然后装饰粒子类(或任何其他类)的成员,如下所示:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(ConditionalRequiredAttribute), typeof(ConditionalRequiredValidator));
Voila,您现在应该能够根据另一个字段的值有条件地验证一个字段。
在旁注中,您可以将此条件逻辑抽象为辅助类,并创建一系列“条件”验证器,ConditionalRequired,ConditionalRange等等......
注意2:虽然它可能比你自己的解决方案更复杂/“更多代码”(在我仍然将这个回复放在一起时发布 - doh!)这确实具有非常可重用的好处。要为将来的视图模型添加相同的功能,您只需使用public class Particle
{
// Mass is required; easy enough.
[Required(ErrorMessage="Mass is required.")]
public double Mass { get; set; }
[ConditionalRequired("Momentum", 0D, ErrorMessage = "Position must be set if Momentum is not.")]
public double Position { get; set; }
[ConditionalRequired("Position", 0D, ErrorMessage = "Momentum must be set if Position is not.")]
public double Momentum { get; set; }
}
属性装饰属性...
答案 2 :(得分:-1)
以为我会发布我带的解决方案。就像Spencerooni所说,没有一种优雅的机制可以为模型本身添加复杂的验证,但这似乎有效。它利用DefaultModelBinder接口的OnModelUpdated方法覆盖来随时更新底层模型。
请注意,只有在回发绑定字段时才会调用模型属性上的DataAnnotations,这意味着验证将传递给没有Mass字段的表单。
Global.asax中
ModelBinders.Binders[typeof(Particle)] = new ParticleModelBinder();
模型/ ParticleModelBinder.cs
public class ParticleModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
var particle = (Particle)bindingContext.Model;
if (<Test for business rule violation here>)
{
var modelState = bindingContext.ModelState;
// Message to appear in validation summary.
modelState.AddModelError("","Please enter position OR momentum.");
// Messages also appear in summary, but highlight the bound controls too.
modelState.AddModelError(bindingContext.ModelName + ".Position",
"Please enter position (or momemtum).");
modelState.AddModelError(bindingContext.ModelName + ".Momentum",
"Please enter momentum (or position).");
}
}
}