Атрибут DataAnnotations "NotRequired"
У меня сложный тип модели.
У меня есть мой UserViewModel
, который имеет несколько свойств, а два из них - HomePhone
и WorkPhone
. Оба типа PhoneViewModel
. В PhoneViewModel
у меня есть CountryCode
, AreaCode
и Number
все строки. Я хочу сделать CountryCode
необязательным, но AreaCode
и Number
обязательным.
Это отлично работает. Моя проблема в том, что в UserViewModel
WorkPhone
является обязательным, а HomePhone
- нет.
В любом случае я могу отменить атрибуты Require
в PhoneViewModel
, установив любые атрибуты в свойстве HomeWork
?
Я пробовал это:
[ValidateInput(false)]
но это только для классов и методов.
код:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public PhoneViewModel HomePhone { get; set; }
[Required]
public PhoneViewModel WorkPhone { get; set; }
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[Required]
public string Number { get; set; }
}
Ответы
Ответ 1
[ОБНОВЛЕНО, 25/24/2012, чтобы сделать идею более понятной)
Я не уверен, что это правильный подход, но я думаю, что вы можете расширить концепцию и создать более общий/многоразовый подход.
В ASP.NET MVC проверка выполняется на этапе привязки. Когда вы отправляете форму на сервер, DefaultModelBinder
- это тот, который создает экземпляры модели из информации запроса и добавляет ошибки проверки в ModelStateDictionary
.
В вашем случае, если привязка происходит с HomePhone
, проверки будут срабатывать, и я думаю, что мы не можем много сделать, создавая специальные атрибуты проверки или аналогичные типы.
Все, о чем я думаю, заключается не в том, чтобы создать экземпляр модели вообще для свойства HomePhone
, когда нет значений, доступных в форме (код isacode, countrycode и number или empty), когда мы контролируем привязку, которую мы контролируем валидацией, для этого нам нужно создать настраиваемое связующее устройство.
В привязке настраиваемой модели мы проверяем, является ли свойство HomePhone
, и если форма содержит любые значения для его свойств, и если мы не привязываем это свойство, и проверки не будут выполняться для HomePhone
. Просто значение HomePhone
будет равно null в UserViewModel
.
public class CustomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "HomePhone")
{
var form = controllerContext.HttpContext.Request.Form;
var countryCode = form["HomePhone.CountryCode"];
var areaCode = form["HomePhone.AreaCode"];
var number = form["HomePhone.Number"];
if (string.IsNullOrEmpty(countryCode) && string.IsNullOrEmpty(areaCode) && string.IsNullOrEmpty(number))
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
Наконец, вам нужно зарегистрировать настраиваемое связующее устройство в файле global.asax.cs.
ModelBinders.Binders.Add(typeof(UserViewModel), new CustomModelBinder());
Итак, теперь у вас есть действие, которое принимает UserViewModel как параметр,
[HttpPost]
public Action Post(UserViewModel userViewModel)
{
}
Наше пользовательское связующее устройство вступает в игру, а в форме не публикуются никакие значения для areacode, countrycode и number для HomePhone
, никаких ошибок проверки не будет, а userViewModel.HomePhone
null. Если форма отправляется по крайней мере в любое из значений для этих свойств, тогда проверка будет выполняться для HomePhone
, как ожидалось.
Ответ 2
Я использую этот удивительный nuget, который делает динамические аннотации: ExpressiveAnnotations
Он позволяет делать то, что было невозможно, например
[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }
или даже public bool GoAbroad {get; задавать; } [RequiredIf ( "GoAbroad == true" )] public string PassportNumber {get; задавать; }
Обновление: компиляция аннотаций в unit test для обеспечения отсутствия ошибок
Как уже упоминалось @diego, это может быть пугающе писать код в строке, но следующее, что я использую для unit test всех проверок, ищущих ошибки компиляции.
namespace UnitTest
{
public static class ExpressiveAnnotationTestHelpers
{
public static IEnumerable<ExpressiveAttribute> CompileExpressiveAttributes(this Type type)
{
var properties = type.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(ExpressiveAttribute)));
var attributes = new List<ExpressiveAttribute>();
foreach (var prop in properties)
{
var attribs = prop.GetCustomAttributes<ExpressiveAttribute>().ToList();
attribs.ForEach(x => x.Compile(prop.DeclaringType));
attributes.AddRange(attribs);
}
return attributes;
}
}
[TestClass]
public class ExpressiveAnnotationTests
{
[TestMethod]
public void CompileAnnotationsTest()
{
// ... or for all assemblies within current domain:
var compiled = Assembly.Load("NamespaceOfEntitiesWithExpressiveAnnotations").GetTypes()
.SelectMany(t => t.CompileExpressiveAttributes()).ToList();
Console.WriteLine($"Total entities using Expressive Annotations: {compiled.Count}");
foreach (var compileItem in compiled)
{
Console.WriteLine($"Expression: {compileItem.Expression}");
}
Assert.IsTrue(compiled.Count > 0);
}
}
}
Ответ 3
Я бы не пошел с modelBinder; Я бы использовал пользовательский атрибут ValidationAttribute:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public HomePhoneViewModel HomePhone { get; set; }
public WorkPhoneViewModel WorkPhone { get; set; }
}
public class HomePhoneViewModel : PhoneViewModel
{
}
public class WorkPhoneViewModel : PhoneViewModel
{
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[CustomRequiredPhone]
public string Number { get; set; }
}
И затем:
[AttributeUsage(AttributeTargets.Property]
public class CustomRequiredPhone : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ValidationResult validationResult = null;
// Check if Model is WorkphoneViewModel, if so, activate validation
if (validationContext.ObjectInstance.GetType() == typeof(WorkPhoneViewModel)
&& string.IsNullOrWhiteSpace((string)value) == true)
{
this.ErrorMessage = "Phone is required";
validationResult = new ValidationResult(this.ErrorMessage);
}
else
{
validationResult = ValidationResult.Success;
}
return validationResult;
}
}
Если это не ясно, я дам объяснение, но я думаю, что это довольно понятно.
Ответ 4
Простое наблюдение: следующий код вызывает проблему, если привязка более чем простая. У меня есть случай, когда в объекте есть вложенный объект, он будет пропускать его и использовать, чтобы некоторые файлы не были привязаны к вложенному объекту.
Возможное решение
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (!propertyDescriptor.Attributes.OfType<RequiredAttribute>().Any())
{
var form = controllerContext.HttpContext.Request.Form;
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).Count() > 0)
{
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).All(
k => string.IsNullOrWhiteSpace(form[k])))
return;
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
благодаря Altaf Khatri