如何在数据库实体的子模型上使用自定义验证属性?

时间:2015-11-03 00:58:50

标签: asp.net-mvc data-annotations dbcontext

要点:

我想要一个数据注释验证器来引用同一个类中的另一个属性(TitleAuthorAndPublishingConfiguration)。

但是,没有直接在此类上调用DB.SaveChanges()。而是在该类的父级(WebsiteConfiguration)上调用它。

因此validationContext.ObjectType正在返回WebsiteConfiguration,我无法在数据注释验证程序中引用TitleAuthorAndPublishingConfiguration的属性。

WebsiteConfiguration.cs

public class WebsiteConfiguration
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    public TitleAuthorAndPublishingConfiguration TitleAuthorAndPublishing { get; set; }

    public BookChaptersAndSectionsConfiguration BookChaptersAndSections { get; set; }

    public SocialMediaLoginsConfiguration SocialMediaLogins { get; set; }

    public TagGroupsConfiguration TagGroups { get; set; }
}

public class TitleAuthorAndPublishingConfiguration 
{
    public string BookTitle { get; set; }

    public bool IsPublished { get; set; }

    // how do I access a property of current model when calling DB.SaveChanges() on parent?
    [RequiredIfOtherFieldIsEnabled("IsPublished")]
    public string Publisher { get; set; }
}

// ... and other sub models...

ApplicationDbContext.cs

DbSet<WebsiteConfiguration> WebsiteConfiguration {get;set;}

示例更新代码

    public void SeedWebsiteConfiguration()
    {
        var titleAuthorAndPublishingConfiguration = new TitleAuthorAndPublishingConfiguration()
        {
            // seed values
        };
        var bookChaptersAndSectionsConfiguration = new BookChaptersAndSectionsConfiguration()
        {
            // seed values
        };
        var socialMediaLoginConfiguration = new SocialMediaLoginsConfiguration()
        {
            // seed values
        };
        var tagGroupsConfiguration = new TagGroupsConfiguration()
        {
            // seed values
        };
        var websiteConfiguration = new WebsiteConfiguration()
        {
            TitleAuthorAndPublishing = titleAuthorAndPublishingConfiguration,
            BookChaptersAndSections = bookChaptersAndSectionsConfiguration,
            SocialMediaLogins = socialMediaLoginConfiguration,
            TagGroups = tagGroupsConfiguration
        };
        DB.WebsiteConfiguration.Add(websiteConfiguration);
        DB.SaveChanges();
    }

验证码

public class RequiredIfOtherFieldIsEnabledAttribute : ValidationAttribute
{
    private string _ifWhatIsEnabled { get; set; }


    public RequiredIfOtherFieldIsEnabledAttribute(string IfWhatIsEnabled)
    {
        _ifWhatIsEnabled = IfWhatIsEnabled;
    }

    protected override ValidationResult IsValid(object currentPropertyValue, ValidationContext validationContext)
    {
        var isEnabledProperty = validationContext.ObjectType.GetProperty(_ifWhatIsEnabled);
        if (isEnabledProperty == null)
        {
            return new ValidationResult(
                string.Format("Unknown property: {0}", _ifWhatIsEnabled)
            );
        }
        var isEnabledPropertyValue = (bool)isEnabledProperty.GetValue(validationContext.ObjectInstance, null);

        if (isEnabledPropertyValue == true)
        {
            if (String.IsNullOrEmpty(currentPropertyValue.ToString()))
            {
                return new ValidationResult(String.Format("This field is required if {0} is enabled", isEnabledProperty));
            }
        }
        return ValidationResult.Success;
    }
}

问题

  1. 我有办法从validationContext访问子模型属性吗?

  2. 我的做法是误入歧途吗?有没有更好的方法将多个模型作为较大模型的一部分存储在单个数据库表中?

  3. 我希望不要有多个配置表和对数据库的调用。 (此示例中有4个子模型,但下一个应用程序中可能有10个以上。)

    上面的设置在很多方面满足了我的需求。但我不想放弃子模型上DataAnnotations的功能!

    奖金问题

    我遇到过像这样的帖子: How can I tell the Data Annotations validator to also validate complex child properties?

    但那已经4岁了,我想知道从那时起是否有任何改变。

    我是否尝试做一些基本上不可能(或至少非常困难)的事情?

1 个答案:

答案 0 :(得分:2)

  

我是否尝试做一些基本上不可能的事情(或者至少是这样   很难)?

不,有一个非常简单的解决方案,可以使用DataAnnotations与框架和技术完美集成。

您可以创建由EF验证调用的自定义ValidationAttribute,并在其中调用Validator.TryValidateObject。这样,当EF调用CustomValidation.IsValid时,您可以手动启动子复杂对象验证,依此类推整个对象图。作为奖励,您可以通过CompositeValidationResult收集所有错误。

using System;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;

public class Program
{
    public static void Main() {
   var person = new Person {
      Address = new Address {
         City = "SmallVille",
         State = "TX",
         Zip = new ZipCode()
      },
      Name = "Kent"
   };

   var context = new ValidationContext(person, null, null);
   var results = new List<ValidationResult>();

   Validator.TryValidateObject(person, context, results, true);

   PrintResults(results, 0);

   Console.ReadKey();
}

private static void PrintResults(IEnumerable<ValidationResult> results, Int32 indentationLevel) {
   foreach (var validationResult in results) {
      Console.WriteLine(validationResult.ErrorMessage);
      Console.WriteLine();

      if (validationResult is CompositeValidationResult) {
         PrintResults(((CompositeValidationResult)validationResult).Results, indentationLevel + 1);
      }
   }
}

}

public class ValidateObjectAttribute: ValidationAttribute {
   protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
      var results = new List<ValidationResult>();
      var context = new ValidationContext(value, null, null);

      Validator.TryValidateObject(value, context, results, true);

      if (results.Count != 0) {
         var compositeResults = new CompositeValidationResult(String.Format("Validation for {0} failed!", validationContext.DisplayName));
         results.ForEach(compositeResults.AddResult);

         return compositeResults;
      }

      return ValidationResult.Success;
   }
}

public class CompositeValidationResult: ValidationResult {
   private readonly List<ValidationResult> _results = new List<ValidationResult>();

   public IEnumerable<ValidationResult> Results {
      get {
         return _results;
      }
   }

   public CompositeValidationResult(string errorMessage) : base(errorMessage) {}
   public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames) {}
   protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult) {}

   public void AddResult(ValidationResult validationResult) {
      _results.Add(validationResult);
   }
}

public class Person {
  [Required]
  public String Name { get; set; }

  [Required, ValidateObject]
  public Address Address { get; set; }
}

public class Address {
  [Required]
  public String Street1 { get; set; }

  public String Street2 { get; set; }

  [Required]
  public String City { get; set; }

  [Required]
  public String State { get; set; }

  [Required, ValidateObject]
  public ZipCode Zip { get; set; }
}

public class ZipCode {
  [Required]
  public String PrimaryCode { get; set; }

  public String SubCode { get; set; }
}