【问题标题】:MVC unobtrusive range validation of dynamic valuesMVC 不显眼的动态值范围验证
【发布时间】:2011-10-21 14:39:39
【问题描述】:

我的模型上有一个值,它必须在我的模型上的其他两个值的范围内。

例如:

public class RangeValidationSampleModel
{
    int Value { get; set; }

    int MinValue { get; set; }

    int MaxValue { get; set; }
}

当然,我不能将这些 Min/MaxValues 传递到我的 DataAnnotations 属性中,因为它们必须是常量值。

我确定我需要构建自己的验证属性,但我还没有做这么多,也无法弄清楚它应该如何工作。

我已经搜索了大约一个小时,并且已经看到了各种用于构建自定义验证的解决方案,但找不到任何东西可以使用 MVC3 非侵入式验证来解决这个特定问题。

【问题讨论】:

  • 这必须是客户端验证吗?
  • 这样会更好。我们正在将此站点从 MVC2 转换为 MVC3,目前,MVC2 验证正在客户端工作,所以我想保持它以这种方式工作。但如果可能的话,我想使用不显眼的验证。当前的验证非常突兀。 :)

标签: asp.net-mvc asp.net-mvc-3 data-annotations unobtrusive-validation


【解决方案1】:

您可以为此编写自定义验证属性:

public class DynamicRangeValidator : ValidationAttribute, IClientValidatable
{
    private readonly string _minPropertyName;
    private readonly string _maxPropertyName;
    public DynamicRangeValidator(string minPropertyName, string maxPropertyName)
    {
        _minPropertyName = minPropertyName;
        _maxPropertyName = maxPropertyName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var minProperty = validationContext.ObjectType.GetProperty(_minPropertyName);
        var maxProperty = validationContext.ObjectType.GetProperty(_maxPropertyName);
        if (minProperty == null)
        {
            return new ValidationResult(string.Format("Unknown property {0}", _minPropertyName));
        }
        if (maxProperty == null)
        {
            return new ValidationResult(string.Format("Unknown property {0}", _maxPropertyName));
        }

        int minValue = (int)minProperty.GetValue(validationContext.ObjectInstance, null);
        int maxValue = (int)maxProperty.GetValue(validationContext.ObjectInstance, null);
        int currentValue = (int)value;
        if (currentValue <= minValue || currentValue >= maxValue)
        {
            return new ValidationResult(
                string.Format(
                    ErrorMessage, 
                    minValue,
                    maxValue
                )
            );
        }

        return null;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ValidationType = "dynamicrange",
            ErrorMessage = this.ErrorMessage,
        };
        rule.ValidationParameters["minvalueproperty"] = _minPropertyName;
        rule.ValidationParameters["maxvalueproperty"] = _maxPropertyName;
        yield return rule;
    }
}

然后用它装饰你的视图模型:

public class RangeValidationSampleModel
{
    [DynamicRangeValidator("MinValue", "MaxValue", ErrorMessage = "Value must be between {0} and {1}")]
    public int Value { get; set; }
    public int MinValue { get; set; }
    public int MaxValue { get; set; }
}

那么你可以有一个控制器服务于一个视图:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new RangeValidationSampleModel
        {
            Value = 5,
            MinValue = 6,
            MaxValue = 8
        });
    }

    [HttpPost]
    public ActionResult Index(RangeValidationSampleModel model)
    {
        return View(model);
    }
}

当然还有观点:

@model RangeValidationSampleModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
    $.validator.unobtrusive.adapters.add('dynamicrange', ['minvalueproperty', 'maxvalueproperty'],
        function (options) {
            options.rules['dynamicrange'] = options.params;
            if (options.message != null) {
                $.validator.messages.dynamicrange = options.message;
            }
        }
    );

    $.validator.addMethod('dynamicrange', function (value, element, params) {
        var minValue = parseInt($('input[name="' + params.minvalueproperty + '"]').val(), 10);
        var maxValue = parseInt($('input[name="' + params.maxvalueproperty + '"]').val(), 10);
        var currentValue = parseInt(value, 10);
        if (isNaN(minValue) || isNaN(maxValue) || isNaN(currentValue) || minValue >= currentValue || currentValue >= maxValue) {
            var message = $(element).attr('data-val-dynamicrange');
            $.validator.messages.dynamicrange = $.validator.format(message, minValue, maxValue);
            return false;
        }
        return true;
    }, '');
</script>

@using (Html.BeginForm())
{
    <div>
        @Html.LabelFor(x => x.Value)
        @Html.EditorFor(x => x.Value)
        @Html.ValidationMessageFor(x => x.Value)
    </div>
    <div>
        @Html.LabelFor(x => x.MinValue)
        @Html.EditorFor(x => x.MinValue)
    </div>
    <div>
        @Html.LabelFor(x => x.MaxValue)
        @Html.EditorFor(x => x.MaxValue)
    </div>
    <button type="submit">OK</button>
}

显然,自定义适配器注册应该在外部 javascript 文件中执行以避免污染视图,但为了这篇文章的目的和简洁性,我将它放在视图中。

【讨论】:

  • 谢谢达林,这太棒了!我确实必须对 javascript 代码进行两个小调整。 isNaN 检查被反转(必须删除 !),我还移动了带有 isNaN 检查的最小/最大检查,以便正确设置消息。
  • 我尝试了您的解决方案并发现了一个严重问题:如果我用您的自定义属性装饰了多个属性,则每个属性都设置为使用不同的错误消息。当两个/所有属性验证失败时,所有属性都会显示完全相同的消息。原因是您的 JavaScript 行 $.validator.messages.dynamicrange = $.format($.validator.messages.dynamicrange, minValue, maxValue); 替换了所有的消息文本。到目前为止,我还没有找到好的解决方案。
  • @hardywang 和你一样有问题。不确定我是否已经解决了它,但我使用 $(element).attr('data-val-dynamicrange') 来获取每个元素的错误消息并使用它设置消息。
  • +1 给 Darin 的建议,但如果模型中不存在该属性,我会抛出异常。这是一个我想记录的错误,让我或任何其他开发人员知道验证存在问题。
  • 我倾向于过度分析这类事情,但我突然想到的一件事是这种解决方案可能会导致意想不到的后果。如果您的值是您允许编辑的屏幕上的实际输入,那么这将适合您。如果您将最小值和最大值属性隐藏输入,那么您可能会向被规避的服务器端验证敞开大门。 \\
【解决方案2】:

自定义验证属性确实是个好主意。类似的东西(挖掘一些sn-p o'mine发现谁知道不久前):

public sealed class MustBeGreaterThan : ValidationAttribute
{
    private const string _defaultErrorMessage = "'{0}' must be greater than '{1}'";
    private string _basePropertyName;

    public MustBeGreaterThan(string basePropertyName)
        : base(_defaultErrorMessage)
    {
        _basePropertyName = basePropertyName;
    }

    //Override default FormatErrorMessage Method
    public override string FormatErrorMessage(string name)
    {
        return string.Format(_defaultErrorMessage, name, _basePropertyName);
    }

    //Override IsValid
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var basePropertyInfo = validationContext.ObjectType.GetProperty(_basePropertyName);
        var lowerBound = (int)basePropertyInfo.GetValue(validationContext.ObjectInstance, null);
        var thisValue = (int)value;

        if (thisValue < lowerBound)
        {
            var message = FormatErrorMessage(validationContext.DisplayName);
            return new ValidationResult(message);
        }

        //value validated
        return null;
    }
}

public sealed class MustBeLowerThan : ValidationAttribute
{
    private const string _defaultErrorMessage = "'{0}' must be lower than '{1}'";
    private string _basePropertyName;

    public MustBeLowerThan(string basePropertyName)
        : base(_defaultErrorMessage)
    {
        _basePropertyName = basePropertyName;
    }

    //Override default FormatErrorMessage Method
    public override string FormatErrorMessage(string name)
    {
        return string.Format(_defaultErrorMessage, name, _basePropertyName);
    }

    //Override IsValid
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var basePropertyInfo = validationContext.ObjectType.GetProperty(_basePropertyName);
        var upperBound = (int)basePropertyInfo.GetValue(validationContext.ObjectInstance, null);
        var thisValue = (int)value;

        if (thisValue > upperBound)
        {
            var message = FormatErrorMessage(validationContext.DisplayName);
            return new ValidationResult(message);
        }

        //value validated
        return null;
    }
}

然后装饰你的班级

public class RangeValidationSampleModel
{
    [MustBeGreaterThan("MinValue")]
    [MustBeLowerThan("MaxValue")]
    int Value { get; set; }

    int MinValue { get; set; }

    int MaxValue { get; set; }
}

你应该很高兴

【讨论】:

  • 谢谢@alex。不过,这似乎不适用于客户端不显眼的验证,不是吗?
【解决方案3】:

如果您需要客户端验证,这必须是自定义的。我最近在这里看到了一篇不错的帖子(Darin Dmitrov?似乎找不到它)无论如何 - 这将允许客户端验证发生: http://blogs.msdn.com/b/simonince/archive/2011/02/04/conditional-validation-in-asp-net-mvc-3.aspx

服务器端可以通过 IValidateableObject 或Dynamic Range Validation in ASP.NET MVC 2处理

在服务器端等等等等,但我觉得你希望这里的客户端是关键。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-18
    • 2016-02-19
    • 2011-07-09
    相关资源
    最近更新 更多