Атрибут 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