【问题标题】:DataAnnotations "NotRequired" attributeDataAnnotations "NotRequired" 属性
【发布时间】:2012-05-30 03:33:11
【问题描述】:

我的模型有点复杂。

我有我的UserViewModel,它有几个属性,其中两个是HomePhoneWorkPhone。都是PhoneViewModel 类型。在PhoneViewModel 我有CountryCodeAreaCodeNumber 所有字符串。我想让CountryCode 可选,但AreaCodeNumber 是强制性的。

这很好用。我的问题是UserViewModelWorkPhone是强制的,而HomePhone不是。

无论如何我可以通过在HomeWork 属性中设置任何属性来禁用PhoneViewModel 中的Require 属性吗?

我试过了:

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

【问题讨论】:

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

标签: c# asp.net-mvc asp.net-mvc-3 data-annotations validationattribute


【解决方案1】:

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

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

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

在您的情况下,只要绑定发生在 HomePhone 上,验证就会启动,我认为我们无法通过创建 自定义验证属性或同类

我的想法是,当 (地区代码、国家代码和数字或空)形式中没有可用值时,根本不为 HomePhone 属性创建模型实例,当我们控制我们控制验证的绑定,为此,我们必须创建一个自定义模型绑定器

自定义模型绑定器中,我们正在检查属性是否为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)
 {

 }

我们的自定义模型绑定器开始发挥作用,表单不会为HomePhone 发布任何区号、国家/地区代码和号码值,不会出现任何验证错误,@987654333 @ 一片空白。如果表单至少发布了这些属性的任何一个值,那么验证将按预期对HomePhone 进行。

【讨论】:

  • 我刚离开家。这究竟是做什么的?我明天试试。
【解决方案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;
    }
}

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

【讨论】:

  • 我认为它更简单,更优雅。也更利于维护
  • 为什么更利于维护??您不必在 global.asay 中注册任何内容,ModelBinder-Solution 使用硬编码字符串,一旦您更改模型属性名称,一切都会中断......如果您没有得到我的解决方法,我会解释它用文字。
  • 我确实得到了你的解决方法。我使用该问题的“通用”解决方案编辑了 asnwer。您必须等待评论才能看到它。它不再使用硬编码的字符串,并且在全局 asax 中的注册(作为类的创建)只是一次。在您的工作区中,每次我有一个新的“子 ViewModel”或者子 ViewModel 的不同用途(即PhoneViewModel Cell)时,我都必须完成所有工作
【解决方案3】:

只是一些观察:如果绑定不仅仅是简单的字段,则以下代码会出现问题。我有一个案例,对象中有嵌套对象,它会跳过它并导致某些文件未绑定到嵌套对象中。

可能的解决方案是

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

【讨论】:

    【解决方案4】:

    我一直在使用这个惊人的 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);
            }
    
    
        }
    }
    

    【讨论】:

    • 看起来很灵活,虽然不太喜欢用字符串写代码
    • 当然,这里也一样,这就是为什么在解决方案启动时,您可以注册以编译所有这些属性以检测任何故障。更多信息:github.com/jwaliszko/…
    • -1。这比创建验证属性有什么好处?这样做的缺点是存在另一个依赖项、魔术字符串和更多无意义的代码。不可重复使用,因为您需要将属性与魔术字符串一起复制和粘贴。
    猜你喜欢
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 2011-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 2012-07-09
    相关资源
    最近更新 更多