- PVSM.RU - https://www.pvsm.ru -

ASP.NET MVC: Условная валидация на клиенте с использованием FluentValidation

Передо мной стояла задача сделать условную валидацию для свойства модели в зависимости от значения другого свойства. Гугл [1] говорит, что такая задача встречается довольно часто. Поэтому я решил поделиться тем, как я решил ее в своем проекте.

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

После некоторого времени проведенного в поиске красивого решения мне попалась библиотека FluentValidation [2]. Прелесть ее в том, что с ее помощью можно легко задавать проверки для модели практически любой сложности, а если возможностей не хватает — можно написать свой валидатор и легко его назначить для нужного свойства. На сайте проекта есть очень хорошая документация [3]. Вот небольшой пример как это работет:

[Validator(typeof(MyModelValidator))]
public class MyModel
{
  public string Name { get; set; }
  public string Description { get; set; }
  public bool IsDescriptionRequired { get; set; }
}

public class MyModelValidator: AbstractValidator<MyModel>
{
  public MyModelValidator()
  {
    RuleFor(m => m.Name).NotEmpty();
    RuleFor(m => m.Description).NotEmpty().When(m => m.IsDescriptionRequired);
  }
}

Для простых условий типа Заполнено, Минимальная длина, Максимальная длина и т.п. валидация на клиенте реализована из-коробки. Для реализации более сложных проверок (в моем случае условной) нужно реализовать свой валидатор свойства и реализовать интерфейс IClientValidatable, о котором есть довольно много инфы [4].

Основная идея в том, что нужно реализовать метод, который возвращает набор данных для jQuery.validate [5].

Вместо реализации нового класса для каждого случая когда нужно реализовать валидацию на клиенте мне хотелось создать универсальный клиентский валидатор, которому можно передать набор правил. Получилось что-то такое:

using GetClientValidationRulesFunc = Func<ModelMetadata, ControllerContext, IEnumerable<ModelClientValidationRule>>;

  class ClientValidator : PropertyValidator, IClientValidatable
  {
    private readonly GetClientValidationRulesFunc _getClientValidationRulesFunc;

    public ClientValidator(GetClientValidationRulesFunc getClientValidationRulesFunc) : base((string)null)
    {
      _getClientValidationRulesFunc = getClientValidationRulesFunc;
    }

    protected override bool IsValid(PropertyValidatorContext context)
    {
      // Suppress any server side validation
      return true;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
      return _getClientValidationRulesFunc(metadata, context);
    }
  }

И использовать в его можно примерно так:

public class MyModelValidator: AbstractValidator<MyModel>
{
  public MyModelValidator()
  {
    RuleFor(m => m.Name).NotEmpty();
    RuleFor(m => m.Description).NotEmpty().When(m => m.IsDescriptionRequired).SetValidator(new ClientValidator(GetValidationRules));
  }

  public IEnumerable<ModelClientValidationRule> GetValidationRules(ModelMetadata metadata,
    ControllerContext context)
  {
    yield return new ModelClientValidationRule
      {
        ErrorMessage = "Description required",
        ValidationType = "validateDescription"
      };
  }
}

Таким образом можно сохранить гибкость библиотеки и отдельно задать правила для клиентской валидации. Еще стоит сказать что для того, чтобы на клиенте валидация тоже выполнялась нужно создать свою функции и адаптер для нее:

$.validator.unobtrusive.adapters.addBool("validateDescription");

$.validator.addMethod("validateDescription", function (value, element, param) { 
  if ( $("#IsDescriptionRequired").val() === "true" ) {
    return $.trim($("#Description").val()).length > 0;
  }
  return true;
});

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

Загрузить пакет можно с помощью NuGet: FluentValidation.Mvc3 с зависимостью FluentValidation

Автор: firestarter


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/asp-net-mvc/9088

Ссылки в тексте:

[1] Гугл: https://www.google.com/#hl=en&sclient=psy-ab&q=asp.net+mvc+conditional+validation&oq=asp.net+mvc+conditional+validation&aq=f&aqi=&aql=&gs_l=hp.3...0.0.6.401073.0.0.0.0.0.0.0.0..0.0...0.0.BBRZxiy_c7U&pbx=1&bav=on.2,or.r_gc.r_pw.r_cp.r_qf.,cf.osb&fp=50e01a6936966f69&biw=1473&bih=774

[2] FluentValidation: http://fluentvalidation.codeplex.com/

[3] документация: http://fluentvalidation.codeplex.com/documentation

[4] много инфы: https://www.google.com/#hl=en&sclient=psy-ab&q=asp.net+mvc+3+iclientvalidatable&oq=asp.net+iclientvalidatable&aq=2bK&aqi=g-K1g-b1g-bK1&aql=&gs_l=hp.3.2.0i30j0i8j0i8i30.0.0.2.1070.0.0.0.0.0.0.0.0..0.0...0.0.57PtyHYVmqk&pbx=1&bav=on.2,or.r_gc.r_pw.r_cp.r_qf.,cf.osb&fp=50e01a6936966f69&biw=1473&bih=774

[5] jQuery.validate: http://docs.jquery.com/Plugins/Validation