Установите флажок "Проверить" на клиенте с FluentValidation/MVC 3

Я пытаюсь проверить, установлен ли флажок на клиенте с помощью FluentValidation. Я не могу понять, что это за жизнь меня.

Можно ли это сделать с помощью ненавязчивой проверки?

Ответы

Ответ 1

Предположим, что у вас есть следующая модель:

[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
    public bool IsChecked { get; set; }
}

со следующим валидатором:

public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
    public MyViewModelValidator()
    {
        RuleFor(x => x.IsChecked).Equal(true).WithMessage("Please check this checkbox");
    }
}

и контроллер:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

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

с соответствующим представлением:

@model MyViewModel
@using (Html.BeginForm())
{
    @Html.LabelFor(x => x.IsChecked)
    @Html.CheckBoxFor(x => x.IsChecked)
    @Html.ValidationMessageFor(x => x.IsChecked)
    <button type="submit">OK</button>
}

а в Global.asax вы зарегистрировали надежного поставщика проверки достоверности модели проверки:

FluentValidationModelValidatorProvider.Configure();

До сих пор мы проверяем и выполняем проверку на стороне сервера. Это хорошо. Это всегда первая часть, которую мы должны настроить. Я видел, что люди слишком много внимания уделяют проверке на стороне клиента, что они забывают о проверке на стороне сервера и когда вы отключите javascript (или, что еще хуже, если вы наткнетесь на пользователя с плохими намерениями), хорошо, что это происходит. До сих пор мы уверены, что знаем, что даже если что-то навязывается клиенту, наш домен защищен проверкой на стороне сервера.


Итак, давайте теперь позаботимся о проверке клиента. Из коробки FluentValidation.NET поддерживает автоматическую проверку клиента для валидатора EqualTo, но при сравнении с другим значением свойства, которое является эквивалентом аннотации данных [Compare].

Но в нашем случае мы сравниваем с фиксированным значением. Таким образом, мы не освобождаем клиентскую сторону из коробки. И когда мы не получаем что-то из коробки, нам нужно положить его в коробку.

Итак, мы начинаем с определения пользовательского FluentValidationPropertyValidator:

public class EqualToValueFluentValidationPropertyValidator : FluentValidationPropertyValidator
{
    public EqualToValueFluentValidationPropertyValidator(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator)
        : base(metadata, controllerContext, rule, validator)
    {
    }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        if (!this.ShouldGenerateClientSideRules())
        {
            yield break;
        }
        var validator = (EqualValidator)Validator;

        var errorMessage = new MessageFormatter()
            .AppendPropertyName(Rule.GetDisplayName())
            .AppendArgument("ValueToCompare", validator.ValueToCompare)
            .BuildMessage(validator.ErrorMessageSource.GetString());

        var rule = new ModelClientValidationRule();
        rule.ErrorMessage = errorMessage;
        rule.ValidationType = "equaltovalue";
        rule.ValidationParameters["valuetocompare"] = validator.ValueToCompare;
        yield return rule;
    }
}

что мы будем регистрироваться в Application_Start:

FluentValidationModelValidatorProvider.Configure(provider =>
{
    provider.AddImplicitRequiredValidator = false;
    provider.Add(typeof(EqualValidator), (metadata, context, description, validator) => new EqualToValueFluentValidationPropertyValidator(metadata, context, description, validator));
});

До сих пор мы связали наш собственный FluentValidationPropertyValidator с EqualValidator.

Последняя часть предназначена для написания пользовательского адаптера:

(function ($) {
    $.validator.unobtrusive.adapters.add('equaltovalue', ['valuetocompare'], function (options) {
        options.rules['equaltovalue'] = options.params;
        if (options.message != null) {
            options.messages['equaltovalue'] = options.message;
        }
    });

    $.validator.addMethod('equaltovalue', function (value, element, params) {
        if ($(element).is(':checkbox')) {
            if ($(element).is(':checked')) {
                return value.toLowerCase() === 'true';
            } else {
                return value.toLowerCase() === 'false';
            }
        }
        return params.valuetocompare.toLowerCase() === value.toLowerCase();
    });
})(jQuery);    

И это в значительной степени. Осталось оставить клиентские скрипты:

<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 src="@Url.Content("~/Scripts/customadapter.js")" type="text/javascript"></script>

Ответ 2

Мне нравится ответ Дарина Димитрова, но если вы хотите сделать это быстро, вот мой альтернативный способ.

Создайте дополнительное свойство в вашей модели, например:

public bool ValidationTrue { get; set; }

и установите его значение true в конструкторе модели.

Используйте его в своем представлении, чтобы сохранить значение по запросам:

@Html.HiddenFor(x => x.ValidationTrue)

Теперь добавьте правило проверки следующим образом:

public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
    public MyViewModelValidator()
    {
        RuleFor(x => x.ValidationTrue)
            .Equal(true); // check it for security reasons, if someone has edited it in the source of the page

        RuleFor(x => x.HasToBeChecked)
            .Equal(x => x.ValidationTrue) // HasToBeChecked has to have the same value as ValidationTrue (which is always true)
            .WithMessage("Required");
    }
}

Эта проверка поддерживается ненавязчивым валидатором из коробки.

Ответ 3

он основан на ответе @cryss

RuleFor(x => x.HasToBeChecked)
        .Equal(x => true)
        .WithMessage("Required");

вам не нужно использовать дополнительное свойство

Ответ 4

Я кодирую в ASP.NET MVC5, а код Дарина создает ошибку javascript в строках, которые ссылаются на value.ToLowerCase() при включенном флажке. Другая проблема заключается в том, что этот код делает недействительным сравнение равенства сторон на стороне клиента между двумя свойствами. Кажется, что это работает только при сравнении с буквальным значением... Возможно, это было его намерением, но мне нужно, чтобы он работал в обеих ситуациях:

Здесь один возможный обходной путь, который включает в себя только два изменения ответа Дарина:

Во-первых, я обновил функцию javascript следующим образом.

$.validator.addMethod('equaltovalue', function (value, element, params) {
    if ($(element).is(':checkbox')) {
        value = $(element).is(':checked') ? "true" : "false";
    }
    return params.valuetocompare.toLowerCase() === value.toLowerCase();
});

Во-вторых, я обновил EqualToValueFluentValidationPropertyValidator следующим образом:

public class EqualToValueFluentValidationPropertyValidator : FluentValidationPropertyValidator
{
    EqualValidator EqualValidator 
    {
        get { return (EqualValidator)Validator; }
    }

    public EqualToValueFluentValidationPropertyValidator(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator) : base(metadata, controllerContext, rule, validator) {
        ShouldValidate = false;
    }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() {
        if (!ShouldGenerateClientSideRules()) yield break;

        var propertyToCompare = EqualValidator.MemberToCompare as PropertyInfo;
        if(propertyToCompare != null) {
            // If propertyToCompare is not null then we're comparing to another property.
            // If propertyToCompare is null then we're either comparing against a literal value, a field or a method call.
            // We only care about property comparisons in this case.

            var comparisonDisplayName =
                ValidatorOptions.DisplayNameResolver(Rule.TypeToValidate, propertyToCompare, null)
                ?? propertyToCompare.Name.SplitPascalCase();

            var formatter = new MessageFormatter()
                .AppendPropertyName(Rule.GetDisplayName())
                .AppendArgument("ComparisonValue", comparisonDisplayName);


            string message = formatter.BuildMessage(EqualValidator.ErrorMessageSource.GetString());
            yield return new ModelClientValidationEqualToRule(message, CompareAttribute.FormatPropertyForClientValidation(propertyToCompare.Name)) ;
        }
        else
        {
            var validator = (EqualValidator)Validator;

            var errorMessage = new MessageFormatter()
                .AppendPropertyName(Rule.GetDisplayName())
                .AppendArgument("ValueToCompare", validator.ValueToCompare)
                .BuildMessage(validator.ErrorMessageSource.GetString());

            var rule = new ModelClientValidationRule();
            rule.ErrorMessage = errorMessage;
            rule.ValidationType = "equaltovalue";
            rule.ValidationParameters["valuetocompare"] = validator.ValueToCompare;
            yield return rule;
        }
    }
}

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

Там могут быть лучшие способы справиться с этим. Если кто-то знает способ непосредственного включения логики из внутреннего класса EqualToFluentValidationPropertyValidator, то я бы хотел его услышать.