MVC ненавязчивая проверка диапазона динамических значений

У меня есть значение моей модели, которое должно находиться в диапазоне двух других значений моей модели.

Например:

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

    int MinValue { get; set; }

    int MaxValue { get; set; }
}

Конечно, я не могу передать эти Min/MaxValues ​​в свои атрибуты DataAnnotations, так как они должны быть постоянными значениями.

Я уверен, что мне нужно создать свой собственный атрибут проверки, но я не сделал этого и не могу понять, как это должно работать.

Я искал около часа и видел всевозможные решения для создания пользовательской проверки, но не могу найти ничего, чтобы решить эту конкретную проблему с помощью ненавязчивой проверки MVC3.


person Jerad Rose    schedule 21.10.2011    source источник
comment
это должно быть проверка на стороне клиента?   -  person Adam Tuliper - MSFT    schedule 21.10.2011
comment
Было бы предпочтительнее. Мы преобразуем этот сайт с MVC2 на MVC3, и в настоящее время проверка MVC2 работает на стороне клиента, поэтому я хотел бы, чтобы она работала таким образом. Но я хотел бы использовать ненавязчивую проверку, если это возможно. Текущая проверка очень навязчива. :)   -  person Jerad Rose    schedule 21.10.2011


Ответы (3)


Для этой цели вы можете написать собственный атрибут проверки:

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, чтобы не загрязнять представление, но для целей и краткости этого поста я поместил его внутрь представления.

person Darin Dimitrov    schedule 21.10.2011
comment
Спасибо Дарин, это круто! Мне пришлось внести две небольшие поправки в код javascript. Проверки isNaN были перевернуты (пришлось удалить !), и я также переместил проверку минимума/максимума с проверками isNaN, чтобы сообщение было установлено правильно. - person Jerad Rose; 22.10.2011
comment
@JeradRose, действительно, в моем коде была ошибка. Я исправил это сейчас. Не стесняйтесь обновлять мой ответ, если вы видите проблемы с ним. - person Darin Dimitrov; 22.10.2011
comment
Спасибо за это. Я думал отредактировать его, но, похоже, у меня пока нет на это прав (думаю, это 2k). - person Jerad Rose; 23.10.2011
comment
Я попробовал ваше решение и обнаружил одну серьезную проблему: если вашим пользовательским атрибутом украшено более одного свойства, для каждого свойства будет использоваться другое сообщение об ошибке. Когда оба/все свойства не проходят проверку, для всех них отображается одно и то же сообщение. Причина в том, что ваша строка JavaScript $.validator.messages.dynamicrange = $.format($.validator.messages.dynamicrange, minValue, maxValue); заменяет тексты сообщений для всех. Пока я еще не нашел хорошего решения. - person hardywang; 22.02.2012
comment
У @hardywang была точно такая же проблема, как у тебя. Не уверен, что мне удалось это обойти, но я использовал $(element).attr('data-val-dynamicrange'), чтобы получить сообщение об ошибке для каждого элемента и установить сообщение, используя это. - person dreza; 03.10.2012
comment
@dreza в конце концов я сдался и начал использовать удаленную проверку. - person hardywang; 03.10.2012
comment
+1 Дарину за рекомендацию, но я бы сделал исключение, если свойство не существует в модели. Это ошибка, которую я хотел бы зарегистрировать, чтобы сообщить мне или другим разработчикам о проблеме с проверкой. - person JustinMichaels; 31.10.2012
comment
Я склонен чрезмерно анализировать подобные вещи, но одна вещь, которая бросилась мне в глаза, заключалась в том, что это решение может привести к непредвиденным последствиям. Если ваше значение является фактическим вводом на экране, который вы разрешаете редактировать, то это сработает для вас. Если вы делаете свойства минимального и максимального значения скрытыми входными данными, то вы потенциально можете открыть себя для обхода проверки на стороне сервера. \\ - person JustinMichaels; 31.10.2012
comment
Великий великий великий Дарин... Я использую его. Как указывает @JustinMichaels, проверку можно обойти на стороне клиента... несмотря на это, вы всегда должны выполнять проверку и на стороне сервера. - person Leniel Maccaferri; 22.08.2013
comment
Я предположил, что проверка на стороне сервера будет выполняться, если проверка на стороне клиента будет обойдена, но не вижу, чтобы это работало. - person R. Schreurs; 18.03.2016

пользовательские атрибуты проверки действительно хорошая мысль. что-то вроде (выкапывая какой-то фрагмент, который я нашел, кто знает, где некоторое время назад):

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

и тебе должно быть хорошо идти

person Alex    schedule 21.10.2011
comment
Спасибо @alex. Однако не похоже, что это будет работать с ненавязчивой проверкой на стороне клиента, не так ли? - person Jerad Rose; 21.10.2011

Если вам нужна проверка на стороне клиента, это должно быть сделано на заказ. Недавно я видел хороший пост здесь (Дарин Дмитров? Кажется, не могу его найти) В любом случае - это позволит выполнить проверку клиента: http://blogs.msdn.com/b/simonince/archive/2011/02/04/условная-валидация-в-asp-net-mvc-3.aspx

Сторона сервера может обрабатываться с помощью IValidateableObject или Dynamic Range Validation в ASP.NET MVC. 2

и т. д. и т. д. на стороне сервера, но я чувствую, что вы хотите, чтобы клиентская сторона была ключом.

person Adam Tuliper - MSFT    schedule 21.10.2011