Подтверждение ввода выглядит довольно неплохо с этим фреймворком
В самом деле? Сценарий, который вы описываете, является прекрасным примером ограничений использования аннотаций данных для проверки.
Я попытаюсь изучить 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 . Это то, что я лично использую и рекомендую.
Итак:
Install-Package FluentValidation.MVC3
в вашей консоли NuGet
В Application_Start
в вашем Global.asax
добавьте следующую строку:
FluentValidationModelValidatorProvider.Configure();
Написать валидатор для модели представления:
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");
}
}
И сама модель представления является 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.