0

学习 MVC 2 NerdDinner 教程时,我看到一些业务逻辑(必填字段、最大长度等)可以添加到模型的各个属性中。

如何添加更复杂的验证规则?例如,需要恰好填充两个属性之一?

示例 [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; }
   }
}
4

3 回答 3

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方法中编写检查。

这是它的用法示例

不幸的是,在MVC2中没有开箱即用的东西允许您在绑定时验证类的多个属性,如MVC3IValidateObject中的接口。您只需要确保在调用or操作时调用附加的 Validate 方法。AddEdit

于 2012-04-20T20:08:58.773 回答
0

听起来您的目标是“条件要求”规则,即根据另一个属性的值标记为必需的属性。仅使用 ValidationAttribute 是不可能的,因为该属性的范围仅限于它所装饰的属性。您需要实现 matchDataAnnotationsModelValidator以具有更广泛的范围(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在某些情况下使用它来调用方法(即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提供者注册,以确保框架在ConditionalRequiredAttribute使用 a 时更喜欢它。为此,请在Application_Start()Global.asax.cs 的方法中添加以下行:

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(ConditionalRequiredAttribute), typeof(ConditionalRequiredValidator));

然后你像这样装饰粒子类(或者实际上是任何其他类)的成员:

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; }
}

瞧,您现在应该能够根据另一个字段的值有条件地验证一个字段。

在旁注中,您可以将此条件逻辑抽象为一个辅助类并创建一系列“条件”验证器,ConditionalRequired,ConditionalRange 等......

注意 2:虽然它可能比您自己的解决方案更复杂/“更多代码”(在我仍在整理此回复时发布 - doh!)这确实具有非常可重用的好处。要将相同的功能添加到未来的视图模型中,您只需使用属性装饰您的ConditionalRequired属性...

于 2012-04-23T14:43:42.993 回答
-1

以为我会发布我使用的解决方案。就像 Spencerooni 所说,没有一种优雅的机制可以为模型本身添加复杂的验证,但这似乎有效。它利用 DefaultModelBinder 接口的 OnModelUpdated 方法覆盖来使基础模型在更新时失效。

请注意,模型属性上的 DataAnnotations 仅在回发绑定字段时调用,这意味着验证将在根本没有 Mass 字段的表单上传递。

全球.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).");
        }
    }
}
于 2012-04-23T13:22:41.013 回答