4

I've got a view which needs several models to work correctly. So, I created a model which is a collection of multiple (sub) models. This is the model.

public class PolicyDetail
{
    public Policy Policy { get; set; }
    public IEnumerable<Insured> Insureds { get; set; }
    public IEnumerable<Risk> Risks { get; set; }
    public IEnumerable<Construction> Constructions { get; set; }
}

And here's an example of what one of the sub models look like, which is an actual entity from the database:

public class Policy
{
    [Key]
    public int PolicyID { get; set; }

    [DisplayName("Policy Number")]
    public Guid PolicyNumber { get; set; }

    [Required(ErrorMessage = "Please enter a valid Effective Date.")]
    [DataType(DataType.DateTime)]
    [DisplayName("Effective Date")]
    [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
    public DateTime EffDate { get; set; }

    [Required(ErrorMessage = "Please enter a valid Expiration Date.")]
    [DataType(DataType.DateTime)]
    [DisplayName("Expiration Date")]
    [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
    public DateTime ExpDate { get; set; }

    public Boolean IsActive { get; set; }
}

This was all working well, right up until I tried to submit a form with errors in it to test the validation. I should have seen this coming (maybe?) but because the actual model doesn't have any validation tags on it, it always passes the if (ModelState.IsValid) check. Is there some way to enforce, or inherit, all of the Data Annotations from the sub classes?

Or, am I going about this all wrong, using a model which is a collection of other models? The thing is, I want to be able to edit/add multiple db entities from the same view.

EDIT:

This article by Josh Carroll looks to be EXACTLY what I need. But when I implement it, I get a Null Object error. Here's what I'm doing:

public class PolicyDetail
{
    [Required, ValidateObject] 
    public Policy Policy { get; set; }
    public IEnumerable<Insured> Insureds { get; set; }
    public IEnumerable<Risk> Risks { get; set; }
    public IEnumerable<Construction> Constructions { get; set; }
}

Then in the override method he provides:

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

the parameter "value" comes in null, so it errors on this line:

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

Am I missing something? Doing something wrong?

Casey Crookston
  • 13,016
  • 24
  • 107
  • 193
  • Possible duplicate of: http://stackoverflow.com/questions/7663501/dataannotations-recursively-validating-an-entire-object-graph – stephen.vakil Mar 25 '16 at 15:09
  • checking that thread. Thanks – Casey Crookston Mar 25 '16 at 15:13
  • Only as an alternative: I recommend taking a look at [FluentValidation](https://github.com/JeremySkinner/FluentValidation) for the non-basic validation cases, for example you could check out [FluentValidation - Validating a View Model that contains a list of an Object](http://stackoverflow.com/a/21310109/4805174) for ideas. – kayess Mar 25 '16 at 15:15
  • @stephen.vakil, thanks for that link. I followed it, and then followed that to the article by Josh Carroll. It looks perfect, but I'm not getting it to work. See my edit in the OP. – Casey Crookston Mar 25 '16 at 16:04

1 Answers1

1

You can manually call the validations on the sub-models using this: https://msdn.microsoft.com/en-us/library/dd411772.aspx

var context = new ValidationContext(model.Policy, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();

bool isValid = Validator.TryValidateObject(model.Policy, context, validationResults, true);

You can then use the ModelState.AddModelError to build the response from that.

Definitely not the most elegant possible solution, but might be easier than rewriting what you have.

Bon
  • 1,083
  • 12
  • 23