Атрибут DataAnnotations "не требуется"
у меня модель довольно сложная.
у меня UserViewModel
, который имеет несколько свойств, и два из них HomePhone
и WorkPhone
. Оба типа PhoneViewModel
. В PhoneViewModel
Я CountryCode
, AreaCode
и Number
все строки. Я хочу сделать CountryCode
необязательно, но AreaCode
и Number
обязательным.
это прекрасно работает. Моя проблема в том, что в UserViewModel
WorkPhone
является обязательным, и HomePhone
нет.
есть ли в любом случае я могу dissable Require
attributs in 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; }
}
4 ответов
[обновлено 24.05.2012, чтобы сделать идею более ясной]
я не уверен, что это правильный подход, но я думаю, что вы можете расширить концепцию и создать более общий / многоразовый подход.
In ASP.NET MVC проверка происходит на этапе привязки. Когда вы отправляете форму на сервер DefaultModelBinder
- Это тот, который создает экземпляры модели из информации запроса и добавляет ошибки проверки в ModelStateDictionary
.
в вашем случае, пока привязка происходит с HomePhone
проверки будут запускаться и Я думаю мы не можем сделать это путем создания пользовательские атрибуты проверки или аналогичный вид.
все, что я думаю, это не создавать экземпляр модели вообще для HomePhone
свойство, когда нет значений, доступных в форме (areacode, countrycode и номер или пустой), когда мы контролируем связывание, мы контролируем проверку, для этого, мы должны создать пользовательская модель binder.
на пользовательская модель binder мы проверяем, является ли свойство 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);
}
}
наконец, вы должны зарегистрировать пользовательскую модель связующего в глобальный.асакс.цезий.
ModelBinders.Binders.Add(typeof(UserViewModel), new CustomModelBinder());
Итак, теперь у вас есть действие, которое принимает UserViewModel в качестве параметра,
[HttpPost]
public Action Post(UserViewModel userViewModel)
{
}
наша пользовательская модель связующего вступают в игру и формы не публикует никаких значений для areacode, countrycode и номер на HomePhone
, там не будет никаких ошибок и userViewModel.HomePhone
равно null. Если форма публикует по крайней мере одно из значений для этих свойств, то проверка произойдет для HomePhone
как и ожидалось.
Я использую этот удивительный nuget, который делает динамические аннотации:ExpressiveAnnotations
это позволяет вам делать вещи, которые не были возможны раньше, такие как
[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }
или даже
public bool GoAbroad { get; set; }
[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }
Update: компиляция аннотаций в модульном тесте для обеспечения отсутствия ошибок
как упоминалось @diego, это может быть пугающим, чтобы написать код в строке, но следующее-Это то, что я использую для модульного тестирования всех проверок ошибка компиляции.
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);
}
}
}
Я бы не пошел с 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;
}
}
Если это не ясно, я предоставлю объяснение, но я думаю, что это довольно понятно.
просто некоторое замечание: следующий код может вызвать проблему, если привязка больше, чем просто подана. У вас есть случай, что в object есть вложенный объект, он пропустит его и caouse, что некоторые файлы не были привязаны к вложенному объекту.
выход
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);
}
спасибо Альтаф Хатри