0

I have a model that references another model and I'd like to validate using data from the referenced model. Is this possible?

Here's my model:

namespace PHAMVC.Models {
  using System.ComponentModel.DataAnnotations;
  using System.ComponentModel.DataAnnotations.Schema;
  using ExpressiveAnnotations.Attributes;

  [Table("Address")]
  public partial class Address {

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int AddressID { get; set; }
    public int PHAMemberID { get; set; }

    [Display(Name = "Address Type (Home/Mailing/Other)")]
    public int AddressTypeID { get; set; }

    [Required]
    [StringLength(60)]
    [Display(Name = "Street")]
    public string AddressLine1 { get; set; }

    [StringLength(60)]
    [Display(Name = " ")]
    public string AddressLine2 { get; set; }

    public int ZipCodeID { get; set; }

    [AssertThat("(AddressTypeID == 1 && DistrictID != 0) || (AddressTypeID != 1)", ErrorMessage = "School District MUST be identified for your Home Address.")]
    [Display(Name = "School District Name")]
    public int DistrictID { get; set; }

    public virtual AddressType AddressType { get; set; }
    public virtual District    District    { get; set; }
    public virtual ZipCode     ZipCode     { get; set; }
  }
}

And here is the District model:

namespace PHAMVC.Models {
  using System;
  using System.Collections.Generic;
  using System.ComponentModel.DataAnnotations;
  using System.ComponentModel.DataAnnotations.Schema;
  using System.Data.Entity.Spatial;

  [Table("District")]
  public partial class District {

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public District() {
      Addresses = new HashSet<Address>();
    }

    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    [Display(Name = "School District Name")]
    public int DistrictID { get; set; }

    [Required]
    [StringLength(50)]
    [Display(Name = "School District Name")]
    public string DistrictName { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    [StringLength(4)]
    public string DistrictCode { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public int? CountyNumber { get; set; }

    public long? NCESDistrictID { get; set; }

    [Required]
    [StringLength(50)]
    public string CountyName { get; set; }

    [Required]
    [StringLength(60)]
    public string StreetAddress { get; set; }

    [Required]
    [StringLength(50)]
    public string City { get; set; }

    [Required]
    [StringLength(2)]
    public string StateCode { get; set; }

    [StringLength(15)]
    public string Phone { get; set; }

    [StringLength(2)]
    public string LocaleCode { get; set; }

    [StringLength(25)]
    public string Locale { get; set; }

    public double? StudentTeacherRatio { get; set; }
    public int?    Students            { get; set; }
    public int?    Teachers            { get; set; }
    public int?    Schools             { get; set; }
    public int     ZIP                 { get; set; }
    public int?    ZIPPlus4            { get; set; }

    [StringLength(25)]
    public string DistrictType { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public bool? isRegularDistrict { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public bool? isRegionalDistrict { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public bool? isStateDistrict { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public bool? isSCCounty { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    [StringLength(100)]
    public string DisplayName { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Address> Addresses { get; set; }
  }
}

What I'm trying to do in the Address Model, conceptually is this:

[AssertThat("ZipCodeID == District.ZIP")]
public int DistrictID { get; set; }

It seems reasonable since the District model is referenced in the Address model, but it produces this exception:

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

 Exception Details: ExpressiveAnnotations.Analysis.ParseErrorException: Parse error on line 1, column 22:
... ZIP ...
    ^--- Only public properties, constants and enums are accepted. Identifier 'ZIP' not known.

Am I missing some obvious way to make District.Zip PUBLIC in the Address model? I'm really new to this, so any guidance or help would be greatly appreciated!

Roy Cross
  • 1
  • 2
  • Where and when are you trying to validate this? Explain how you reproduce your error. This seems like business logic -- just compare the values yourself before committing to the database. – Jasen Dec 21 '16 at 17:32
  • I am trying to validate in the model, just like [Required] or [Requiredif] type of validation tags. The exception error displays as soon as I hit SUBMIT and the validation part of MVC executes. I suppose I could compare the two in the Controller where I know I have the values readily available, but then I have validation in two places. I thought the idea was to keep them all in one location? – Roy Cross Dec 21 '16 at 19:13
  • 2
    First, I would use the [ViewModel pattern](http://stevemichelotti.com/aspnet-mvc-view-model-patterns/) and implement [IValidatableObject](https://weblogs.asp.net/scottgu/class-level-model-validation-with-ef-code-first-and-asp-net-mvc-3) in there. Much cleaner separation of concerns IMO. – Steve Greene Dec 21 '16 at 19:24
  • 1
    [View Models](http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc) will avoid situations like this. – Jasen Dec 21 '16 at 20:14
  • I agree that a ViewModel is the proper solution, however, I'm foggy on how the two domain views are utilised in the ViewModel and what the db.??? calls look like in the controller. Is the ViewModel simply a 'container' that I have to fill from each domain model it references? – Roy Cross Dec 21 '16 at 21:21
  • Yes, you map fields yourself from View Models to Domain Models `vm.StreetAddress = dm.StreetAddress`. There might not even be a 1:1 composition of View Model to Domain Model. Tools like [AutoMapper](https://github.com/AutoMapper/AutoMapper/wiki/Getting-started) make this less manual. – Jasen Dec 21 '16 at 22:20

0 Answers0