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

Mar*_*nox 12 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...
Run Code Online (Sandbox Code Playgroud)

ApplicationDbContext.cs

DbSet<WebsiteConfiguration> WebsiteConfiguration {get;set;}
Run Code Online (Sandbox Code Playgroud)

示例更新代码

    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();
    }
Run Code Online (Sandbox Code Playgroud)

验证码

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

问题

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

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

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

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


奖金问题

我遇到过这样的一些帖子: 我如何告诉Data Annotations验证器还验证复杂的子属性?

但那已经4年了,我想知道从那以后发生了什么变化.

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

jlv*_*ero 2

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

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

您可以创建一个ValidationAttribute由 EF Validation 调用的自定义并Validator.TryValidateObject在内部调用。这样,当CustomValidation.IsValidEF 调用时,您可以手动启动子复杂对象验证,等等,以针对整个对象图。作为奖励,您可以通过 收集所有错误CompositeValidationResult

IE

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; }
}
Run Code Online (Sandbox Code Playgroud)