Как использовать аннотации данных для выполнения условной проверки на модели?
Например, скажем, у нас есть следующая модель (Person and Senior):
public class Person
{
[Required(ErrorMessage = "*")]
public string Name
{
get;
set;
}
public bool IsSenior
{
get;
set;
}
public Senior Senior
{
get;
set;
}
}
public class Senior
{
[Required(ErrorMessage = "*")]//this should be conditional validation, based on the "IsSenior" value
public string Description
{
get;
set;
}
}
И следующее представление:
<%= Html.EditorFor(m => m.Name)%>
<%= Html.ValidationMessageFor(m => m.Name)%>
<%= Html.CheckBoxFor(m => m.IsSenior)%>
<%= Html.ValidationMessageFor(m => m.IsSenior)%>
<%= Html.CheckBoxFor(m => m.Senior.Description)%>
<%= Html.ValidationMessageFor(m => m.Senior.Description)%>
Я хотел бы быть обязательным полем свойства "Senior.Description", основанным на выборе свойства "IsSenior" (true → required). Как реализовать условную проверку в ASP.NET MVC 2 с аннотациями данных?
Я решил это, обратившись к "ModelState" , который содержится в контроллере. Словарь ModelState включает все члены, которые должны быть проверены.
Вот решение:
Если вам нужно реализовать условную проверку на основе некоторого поля (например, если A = true, тогда требуется B), при сохранении сообщений об ошибках уровня собственности (это неверно для пользовательских валидаторов, находящихся на уровне объекта), вы можете достичь этого, обработав "ModelState", просто удалив из него нежелательные проверки.
... В некотором классе...
public bool PropertyThatRequiredAnotherFieldToBeFilled
{
get;
set;
}
[Required(ErrorMessage = "*")]
public string DepentedProperty
{
get;
set;
}
... класс продолжается...
... В некоторых действиях контроллера...
if (!PropertyThatRequiredAnotherFieldToBeFilled)
{
this.ModelState.Remove("DepentedProperty");
}
...
При этом мы достигаем условной проверки, оставляя все остальное тем же.
UPDATE:
Это моя последняя реализация: я использовал интерфейс для модели и атрибут action, который проверяет модель, которая реализует указанный интерфейс. Интерфейс предписывает метод Validate (ModelStateDictionary modelState). Атрибут action просто вызывает Validate (modelState) на странице IValidatorSomething.
Я не хотел усложнять этот ответ, поэтому я не упомянул о последних деталях реализации (которые, в конце концов, имеют значение в производственном коде).
есть намного лучший способ добавить правила условной валидации в MVC3. Пусть ваша модель наследует объект IValidatableObject и реализует метод Validate:
public class Person : IValidatableObject
{
public string Name { get; set; }
public bool IsSenior { get; set; }
public Senior Senior { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsSenior && string.IsNullOrEmpty(Senior.Description))
yield return new ValidationResult("Description must be supplied.");
}
}
подробнее см. описание http://weblogs.asp.net/scottgu/archive/2010/07/27/introducing-asp-net-mvc-3-preview-1.aspx
У меня была такая же проблема вчера, но я сделал это очень простым способом, который работает как на стороне клиента, так и на стороне сервера.
Условие: исходя из значения другого свойства в модели, вы хотите сделать еще одно свойство. Вот код
public class RequiredIfAttribute : RequiredAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if (proprtyvalue.ToString() == DesiredValue.ToString())
{
ValidationResult result = base.IsValid(value, context);
return result;
}
return ValidationResult.Success;
}
}
Здесь PropertyName - это свойство, на котором вы хотите сделать свое условие DesiredValue - это конкретное значение свойства PropertyName (свойство), для которого ваше другое свойство должно быть подтверждено для требуемого
Скажите, что у вас есть следующие
public class User
{
public UserType UserType { get; set; }
[RequiredIf("UserType", UserType.Admin, ErrorMessageResourceName = "PasswordRequired", ErrorMessageResourceType = typeof(ResourceString))]
public string Password
{
get;
set;
}
}
Наконец, но не в последнюю очередь, зарегистрируйте адаптер для вашего атрибута, чтобы он мог выполнять проверку на стороне клиента (я помещал его в global.asax, Application_Start)
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),typeof(RequiredAttributeAdapter));
Я использую этот удивительный nuget, который делает динамические аннотации ExpressiveAnnotations
Вы можете проверить любую логику, о которой вы можете мечтать:
public string Email { get; set; }
public string Phone { get; set; }
[RequiredIf("Email != null")]
[RequiredIf("Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact { get; set; }
Вы можете отключить валидаторы условно, удалив ошибки из ModelState:
ModelState["DependentProperty"].Errors.Clear();
Спасибо Merritt:)
Я только что обновил это до MVC 3, если кто-нибудь найдет его полезным; http://blogs.msdn.com/b/simonince/archive/2011/02/04/conditional-validation-in-asp-net-mvc-3.aspx
Саймон
В настоящее время существует фреймворк, который делает эту условную проверку (среди других удобных проверок аннотаций) из коробки: http://foolproof.codeplex.com/
В частности, посмотрите на валидатор [RequiredIfTrue ( "IsSenior" )]. Вы помещаете это непосредственно в свойство, которое хотите проверить, так что вы получите желаемое поведение ошибки проверки, связанной с свойством "Senior".
Он доступен как пакет NuGet.
У меня была такая же проблема, мне нужна модификация атрибута [Обязательный] - введите поле в зависимости от http-запроса. Решение было похоже на ответ Дэна Хунэса, но его решение не сработало правильно (см. комментарии). Я не использую ненавязчивую проверку, просто MicrosoftMvcValidation.js из коробки. Вот. Внесите свой собственный атрибут:
public class RequiredIfAttribute : RequiredAttribute
{
public RequiredIfAttribute(/*You can put here pararmeters if You need, as seen in other answers of this topic*/)
{
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
//You can put your logic here
return ValidationResult.Success;//I don't need its server-side so it always valid on server but you can do what you need
}
}
Затем вам нужно реализовать своего настраиваемого поставщика, чтобы использовать его в качестве адаптера в своем global.asax
public class RequreIfValidator : DataAnnotationsModelValidator <RequiredIfAttribute>
{
ControllerContext ccontext;
public RequreIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
ccontext = context;// I need only http request
}
//override it for custom client-side validation
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
//here you can customize it as you want
ModelClientValidationRule rule = new ModelClientValidationRule()
{
ErrorMessage = ErrorMessage,
//and here is what i need on client side - if you want to make field required on client side just make ValidationType "required"
ValidationType =(ccontext.HttpContext.Request["extOperation"] == "2") ? "required" : "none";
};
return new ModelClientValidationRule[] { rule };
}
}
И измените свой global.asax на строку
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequreIfValidator));
и здесь
[RequiredIf]
public string NomenclatureId { get; set; }
Основным преимуществом для меня является то, что мне не нужно кодировать пользовательский валидатор клиента, как в случае ненавязчивой проверки. он работает так же, как [Требуется], но только в тех случаях, когда вы хотите.
DataAnnotationsModelValidator
была именно тем, что мне нужно было увидеть. Спасибо.
Посмотрите на этого парня:
http://blogs.msdn.com/b/simonince/archive/2010/06/04/conditional-validation-in-mvc.aspx
Сейчас я работаю над своим примером.
Вам необходимо пройти проверку на уровне Лица, а не на старшем уровне, или Старший должен иметь ссылку на своего родителя. Мне кажется, что вам нужен механизм проверки подлинности, который определяет валидацию для Лица, а не одного из его свойств. Я не уверен, но я не думаю, что DataAnnotations поддерживает это из коробки. Что вы можете сделать, создайте свой собственный Attribute
, который выводится из ValidationAttribute
, который может быть украшен на уровне класса, а затем создайте настраиваемый валидатор, который также позволяет запускать эти валидаторы на уровне классов.
Я знаю, что блок Application Validation поддерживает самооценку из коробки, но VAB имеет довольно крутую кривую обучения. Тем не менее, здесь пример использования VAB:
[HasSelfValidation]
public class Person
{
public string Name { get; set; }
public bool IsSenior { get; set; }
public Senior Senior { get; set; }
[SelfValidation]
public void ValidateRange(ValidationResults results)
{
if (this.IsSenior && this.Senior != null &&
string.IsNullOrEmpty(this.Senior.Description))
{
results.AddResult(new ValidationResult(
"A senior description is required",
this, "", "", null));
}
}
}
Я использую MVC 5, но вы можете попробовать что-то вроде этого:
public DateTime JobStart { get; set; }
[AssertThat("StartDate >= JobStart", ErrorMessage = "Time Manager may not begin before job start date")]
[DisplayName("Start Date")]
[Required]
public DateTime? StartDate { get; set; }
В вашем случае вы скажете что-то вроде "IsSenior == true". Затем вам просто нужно проверить валидацию в своем посту.
Типичное использование для условного удаления ошибки из состояния модели:
Пример:
public ActionResult MyAction(MyViewModel vm)
{
// perform conditional test
// if true, then remove from ModelState (e.g. ModelState.Remove("MyKey")
// Do typical model state validation, inside following if:
// if (!ModelState.IsValid)
// Do rest of logic (e.g. fetching, saving
В вашем примере сохраните все как есть и добавьте логику, предложенную вашему Action Controller. Я предполагаю, что ваш ViewModel, переданный в действие контроллера, имеет объекты Person и Senior Person с данными, заполненными ими из пользовательского интерфейса.
Senior
объект всегда старший, так почему IsSenior может быть ложным в этом случае. Разве вам не нужно, чтобы свойство Person.Senior было нулевым, еслиPerson.IsSenior
имеет значение false. Или почему бы не реализовать свойствоIsSenior
следующим образом:bool IsSenior { get { return this.Senior != null; } }
.