问题 DataAnnotations“NotRequired”属性


我有一种复杂的模型。

我有我的 UserViewModel 它有几个属性,其中两个是 HomePhone 和 WorkPhone。两种类型 PhoneViewModel。在 PhoneViewModel 我有 CountryCodeAreaCode 和 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; }
}

12757
2018-05-23 14:49


起源

你能展示你的模特/视图模型的代码吗? - CallumVass
是的,当然。给我几分钟,因为我写了一个例子,这不完全是我的情况。 - Diego
好吧,我可能不会在接下来的一个小时内回答因为会议所以希望其他人可以! - CallumVass
好。这里是! - Diego
作为参考,ValidateInput属性未命名为最佳 - 它实际上更像是安全过滤器,而不是模型验证。它验证了安全性,而不是“正确性”。来自MSDN:“[ValidateInput]的工作原理是根据潜在危险数据的硬编码列表检查所有输入数据。” - Leniency


答案:


[2012年5月24日更新,以使想法更清晰]

我不确定这是正确的方法,但我认为你可以扩展这个概念,并可以创建一个更通用/可重用的方法。

在ASP.NET MVC中,验证发生在绑定阶段。当您将表单发布到服务器时 DefaultModelBinder 是从请求信息创建模型实例并将验证错误添加到 ModelStateDictionary

在你的情况下,只要绑定发生在 HomePhone 验证将启动和 我认为 我们不能通过创造来做很多事情 自定义验证属性或类似的类型

我所想的只是根本不创建模型实例 HomePhone 表格中没有可用值时的属性 (areacode,国家代码和数字或空)当我们控制绑定时,我们控制验证,为此,我们必须创建一个 定制模型活页夹

在里面 定制模型活页夹 我们正在检查房产是否属于 HomePhone 如果表单包含其属性的任何值,如果不包含,我们不绑定该属性,并且不会发生验证 HomePhone。简单地说,价值 HomePhone 将在null中为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,国家代码和号码 对于 HomePhone,不会有任何验证错误和 userViewModel.HomePhone 一片空白。如果表单至少发布这些属性的任何一个值,则验证将发生 HomePhone 正如所料。


5
2018-05-23 17:28



我刚离开家。这究竟是做什么的?我明天会试试。 - Diego
非常好!!!谢谢! - Diego


我一直在使用这个做动态注释的神奇的nuget: ExpressiveAnnotations

它允许你做以前不可能做的事情

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

甚至

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

更新:在单元测试中编译注释以确保不存在错误

正如@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);
        }


    }
}

3
2018-04-20 12:43



看起来很灵活,不太喜欢用字符串编写代码 - Diego
当然,在这里,这就是为什么在解决方案启动时,您可以注册编译所有这些属性以检测任何故障。更多信息: github.com/jwaliszko/... - Korayem
@diego我添加了我的单元测试类,就是这样。希望能帮助到你.. - Korayem
-1。这比创建验证属性更好吗?这样做的缺点是还有另一个依赖项,魔术字符串和更多无意义的代码。不可重用,因为您将复制并粘贴属性以及魔术字符串。 - ManselD


我不会选择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;
    }
}

如果不清楚,我会提供一个解释,但我认为这是不言自明的。


2
2018-05-24 09:17



我认为另一种方式更简单,更优雅。此外,它更适合维护 - Diego
为什么维护更好?您不必在global.asay中注册任何内容,ModelBinder-Solution可以使用硬编码字符串,一旦您更改了模型属性名称,一切都会中断...如果您没有得到我的解决方法,我会解释它用文字。 - iappwebdev
我确实得到了你的工作。我使用针对该问题的“通用”解决方案编辑了asnwer。您必须等待审核才能看到它。它不再是硬编码字符串,全局asax(作为类的创建)中的注册只是一次。在你的工作环境中,每次我有一个新的“子ViewModel”或者对子ViewModel的不同用法时,我必须做所有的工作(即 PhoneViewModel Cell) - Diego


只是一些观察:如果绑定不仅仅是简单的提交,下面的代码就会出现问题。我有一个案例,在对象中有嵌套对象,它将跳过它并且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);
     }

非常感谢 Altaf Khatri


1
2017-08-26 20:13