MVC3 зависимая от входа проверка - PullRequest
7 голосов
/ 14 февраля 2012

Примечание: я относительно новичок в MVC3.
Проверка входных данных выглядит довольно хорошо с этой средой, где вы можете просто сказать [Обязательный], и проверка на стороне клиента и на сервере просто работает оттуда. Но что, если я хотел бы реализовать условную валидацию?

Сценарий: у меня будет выпадающий список, в котором вам нужно выбрать один из 2 вариантов. Если выбрана опция 1, появятся 2 поля ввода текста, и оба поля обязательны для заполнения. Если выбран вариант 2, появятся 2 переключателя, и вам необходимо выбрать одну из них. Как валидация MVC3 может достичь этого?

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

1 Ответ

18 голосов
/ 14 февраля 2012

Подтверждение ввода выглядит довольно неплохо с этим фреймворком

В самом деле? Сценарий, который вы описываете, является прекрасным примером ограничений использования аннотаций данных для проверки.

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

Позвольте мне непосредственно перед началом их исследования показать контроллер и представление, которое будет использоваться для 3 сценариев, поскольку они будут одинаковыми.

Контроллер:

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

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

Вид:

@model MyViewModel

@using (Html.BeginForm())
{
    <div>
        @Html.LabelFor(x => x.SelectedOption)
        @Html.DropDownListFor(
            x => x.SelectedOption, 
            Model.Options, 
            "-- select an option --", 
            new { id = "optionSelector" }
        )
        @Html.ValidationMessageFor(x => x.SelectedOption)
    </div>
    <div id="inputs"@Html.Raw(Model.SelectedOption != "1" ? " style=\"display:none;\"" : "")>
        @Html.LabelFor(x => x.Input1)   
        @Html.EditorFor(x => x.Input1)
        @Html.ValidationMessageFor(x => x.Input1)

        @Html.LabelFor(x => x.Input2)
        @Html.EditorFor(x => x.Input2)
        @Html.ValidationMessageFor(x => x.Input2)
    </div>
    <div id="radios"@Html.Raw(Model.SelectedOption != "2" ? " style=\"display:none;\"" : "")>
        @Html.Label("rad1", "Value 1")
        @Html.RadioButtonFor(x => x.RadioButtonValue, "value1", new { id = "rad1" })

        @Html.Label("rad2", "Value 2")
        @Html.RadioButtonFor(x => x.RadioButtonValue, "value2", new { id = "rad2" })

        @Html.ValidationMessageFor(x => x.RadioButtonValue)
    </div>
    <button type="submit">OK</button>
}

сценарий:

$(function () {
    $('#optionSelector').change(function () {
        var value = $(this).val();
        $('#inputs').toggle(value === '1');
        $('#radios').toggle(value === '2');
    });
});

Ничего особенного здесь. Контроллер, который создает экземпляр модели представления, которая передается в представление. В представлении у нас есть форма и выпадающий список. Используя javascript, мы подписываемся на событие изменения этого выпадающего списка и переключаем различные области этой формы на основе выбранного значения.


Возможность 1

Первая возможность состоит в том, чтобы ваша модель представления реализовала IValidatableObject . Имейте в виду, что если вы решите реализовать этот интерфейс в своей модели представления, вам не следует использовать какие-либо атрибуты проверки в свойствах модели представления, иначе метод Validate никогда не будет вызываться:

public class MyViewModel: IValidatableObject
{
    public string SelectedOption { get; set; }
    public IEnumerable<SelectListItem> Options
    {
        get
        {
            return new[]
            {
                new SelectListItem { Value = "1", Text = "item 1" },
                new SelectListItem { Value = "2", Text = "item 2" },
            };
        }
    }

    public string RadioButtonValue { get; set; }

    public string Input1 { get; set; }
    public string Input2 { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (SelectedOption == "1")
        {
            if (string.IsNullOrEmpty(Input1))
            {
                yield return new ValidationResult(
                    "Input1 is required", 
                    new[] { "Input1" }
                );
            }
            if (string.IsNullOrEmpty(Input2))
            {
                yield return new ValidationResult(
                    "Input2 is required",
                    new[] { "Input2" }
                );
            }
        }
        else if (SelectedOption == "2")
        {
            if (string.IsNullOrEmpty(RadioButtonValue))
            {
                yield return new ValidationResult(
                    "RadioButtonValue is required",
                    new[] { "RadioButtonValue" }
                );
            }
        }
        else
        {
            yield return new ValidationResult(
                "You must select at least one option", 
                new[] { "SelectedOption" }
            );
        }
    }
}

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


Возможность 2

Другая возможность - написать собственный атрибут проверки, например [RequiredIf]:

[AttributeUsageAttribute(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true)]
public class RequiredIfAttribute : RequiredAttribute
{
    private string OtherProperty { get; set; }
    private object Condition { get; set; }

    public RequiredIfAttribute(string otherProperty, object condition)
    {
        OtherProperty = otherProperty;
        Condition = condition;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(OtherProperty);
        if (property == null)
            return new ValidationResult(String.Format("Property {0} not found.", OtherProperty));

        var propertyValue = property.GetValue(validationContext.ObjectInstance, null);
        var conditionIsMet = Equals(propertyValue, Condition);
        return conditionIsMet ? base.IsValid(value, validationContext) : null;
    }
}

и затем:

public class MyViewModel
{
    [Required]
    public string SelectedOption { get; set; }
    public IEnumerable<SelectListItem> Options
    {
        get
        {
            return new[]
            {
                new SelectListItem { Value = "1", Text = "item 1" },
                new SelectListItem { Value = "2", Text = "item 2" },
            };
        }
    }

    [RequiredIf("SelectedOption", "2")]
    public string RadioButtonValue { get; set; }

    [RequiredIf("SelectedOption", "1")]
    public string Input1 { get; set; }
    [RequiredIf("SelectedOption", "1")]
    public string Input2 { get; set; }
}

Что хорошо в этом подходе, так это то, что наша модель представления чиста. Что в этом плохого, так это то, что при использовании пользовательских атрибутов проверки вы можете быстро выйти за пределы. Подумайте, например, о более сложных сценариях, в которых вам нужно вернуться к субмоделям, коллекциям и прочему. Это быстро превратится в беспорядок.


Возможность 3

Третья возможность - использовать FluentValidation.NET . Это то, что я лично использую и рекомендую.

Итак:

  1. Install-Package FluentValidation.MVC3 в вашей консоли NuGet
  2. В Application_Start в вашем Global.asax добавьте следующую строку:

    FluentValidationModelValidatorProvider.Configure();
    
  3. Написать валидатор для модели представления:

    public class MyViewModelValidator : AbstractValidator<MyViewModel>
    {
        public MyViewModelValidator()
        {
            RuleFor(x => x.SelectedOption).NotEmpty();
            RuleFor(x => x.Input1).NotEmpty().When(x => x.SelectedOption == "1");
            RuleFor(x => x.Input2).NotEmpty().When(x => x.SelectedOption == "1");
            RuleFor(x => x.RadioButtonValue).NotEmpty().When(x => x.SelectedOption == "2");
        }
    }
    
  4. И сама модель представления является POCO:

    [Validator(typeof(MyViewModelValidator))]
    public class MyViewModel
    {
        public string SelectedOption { get; set; }
        public IEnumerable<SelectListItem> Options
        {
            get
            {
                return new[]
                {
                    new SelectListItem { Value = "1", Text = "item 1" },
                    new SelectListItem { Value = "2", Text = "item 2" },
                };
            }
        }
    
        public string RadioButtonValue { get; set; }
        public string Input1 { get; set; }
        public string Input2 { get; set; }
    }
    

Что хорошо в этом, так это то, что у нас есть идеальное разделение между проверкой и моделью представления. прекрасно интегрируется с ASP.NET MVC . Мы можем модульный тест нашего валидатора изолированно, очень просто и быстро.

Что плохого в этом, так это то, что когда Microsoft создавала ASP.NET MVC, она выбрала логику декларативной проверки (с использованием аннотаций данных) вместо императивной, которая намного лучше подходит для сценариев проверки и может обрабатывать все что угодно. Плохо, что FluentValidation.NET на самом деле не является стандартным способом для проверки в ASP.NET MVC.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...