Вопрос по c#, asp.net-mvc, asp.net-mvc-3 – Атрибут DataAnnotations «NotRequired»

11

У меня сложная модель.

У меня есть мой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; }
}
Хорошо. Вот! Diego
Для справки, атрибут ValidateInput не назван лучшим - на самом деле это скорее фильтр безопасности, а не проверка модели. Это подтверждает безопасность, а не «правильность». Из MSDN: & quot; [ValidateInput] работает путем проверки всех входных данных по жестко закодированному списку потенциально опасных данных. & Quot; Leniency
Можете ли вы показать код ваших моделей / viewmodels, пожалуйста? CallumVass
Хорошо, я не могу ответить в течение следующего часа, так что из-за встречи, надеюсь, кто-то еще может! CallumVass
Да, конечно. Дайте несколько минут, потому что я написал пример, который НЕ ТОЛЬКО мой случай. Diego

Ваш Ответ

4   ответа
1

Небольшое наблюдение: следующий код может вызвать проблему, если привязка более чем проста. Если у вас есть случай, когда у объекта есть вложенный объект, он пропустит его и может привести к тому, что некоторые поля не были связаны во вложенном объекте.

Возможное решение

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);
     }

большое спасибоАльтаф Хатри

5

[UPDATED on 5/24/2012 to make the idea more clear]

Я не уверен, что это правильный подход, но я думаю, что вы можете расширить концепцию и создать более общий / повторно используемый подход.

В ASP.NET MVC проверка происходит на этапе привязки. Когда вы отправляете форму на серверDefaultModelBinder это тот, который создает экземпляры модели из информации запроса и добавляет ошибки проверки вModelStateDictionary.

В вашем случае, если привязка происходит сHomePhone проверки будут запущены иI think мы не можем многое сделать с этим путем созданияcustom validation attributes or similar kind.

Все, что я думаю, - это вообще не создавать модель экземпляра дляHomePhone свойство, когда в форме нет доступных значений(the areacode, countrycode and number or empty)Когда мы контролируем привязку, мы контролируем проверку, для этого мы должны создатьcustom model binder.

вcustom model binder мы проверяем, если свойствоHomePhone и если форма содержит какие-либо значения для ее свойств, а если нет, то мы не привязываем свойство, и проверки не будут выполняться дляHomePhone, Просто значениеHomePhone будет нулевым в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 and number заHomePhoneне будет никаких ошибок валидации иuserViewModel.HomePhone нулевой. Если форма отправляет хотя бы одно из значений этих свойств, то проверка будет выполняться дляHomePhone как и ожидалось.

Действительно мило!!! Спасибо! Diego
Я только что ушел из дома. Что именно это делает? Я попробую завтра. Diego
2

Я бы не пошел с 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;
    }
}

Если это не ясно, я предоставлю объяснение, но я думаю, что это довольно очевидно.

Я думаю, что это намного проще и элегантнее с другой стороны. Кроме того, это лучше для обслуживания Diego
Почему это лучше для обслуживания? Вам не нужно ничего регистрировать в global.asay, ModelBinder-Solution работает с жестко закодированными строками, как только вы меняете имена свойств вашей модели, все ломается ... Если вы не нашли мой обходной путь, я объясню это , со словами.
Я получил ваш обходной путь. Я отредактировал asnwer с помощью "generic" решение проблемы. Вы должны дождаться обзора, чтобы увидеть его. Это больше не связано с жестко закодированными строками, и регистрация в глобальном asax (как создание класса) выполняется только один раз. В вашем обходном пути я должен выполнять всю работу каждый раз, когда у меня появляется новый & quot; дочерний ViewModel & quot; или другое использование дочерней ViewModel (т.е.PhoneViewModel Cell) Diego
3

Я использовал этот удивительный нюгет, который делает динамические аннотации:ExpressiveAnnotations

Это позволяет вам делать вещи, которые раньше были невозможны, такие как

[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }

или даже

public bool GoAbroad { get; set; }
[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }

Update: Compile annotations in a unit test to ensure no errors exist

Как уже упоминалось @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);
        }


    }
}
-1. Чем это лучше, чем создание атрибута проверки? Недостатком этого является то, что есть еще одна зависимость, магические строки и более бессмысленный код. Не подлежит повторному использованию, поскольку вы копируете и вставляете атрибут вместе с волшебной строкой.
Кажется очень гибким, хотя я действительно не люблю писать код в строке Diego
@diego Я добавил свой класс модульных тестов, который делает именно это. Надеюсь, поможет..
Конечно, то же самое здесь, поэтому при запуске решения вы можете зарегистрироваться, чтобы скомпилировать все эти атрибуты для обнаружения любых сбоев. Больше здесь:github.com/jwaliszko/…

Похожие вопросы