How to Tell the Data Annotations Validator to Also Validate Complex Child Properties

How can I tell the Data Annotations validator to also validate complex child properties?

You will need to make your own validator attribute (eg, [CompositeField]) that validates the child properties.

How Can I Use Custom Validation Attributes on Child Models of a DB Entity?

Am I trying to do something that is basically impossible (or at least
very difficult)?

No, there is a very simple solution that integrates perfectly with the framework and technologies using DataAnnotations.

You can create a custom ValidationAttribute that is called by EF Validation and call Validator.TryValidateObject inside. This way, when CustomValidation.IsValid is called by EF you launch child complex object validation by hand and so on for the whole object graph. As a bonus, you can gather all errors thanks to CompositeValidationResult.

i.e.

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

Validate only few properties of a model class using Data Annotations from a Console application in c#

If you have a different part of the app that requires a different set or properties and validation rules, then you should be using a completely distinct model for that purpose. So you could have a new model like this:

public class UserCredentials
{
[Required, EmailAddress, MaxLength(256), Display(Name = "Email Address")]
public string Email {get;set;}

[Required, MaxLength(20), DataType(DataType.Password), Display(Name ="Password")]
public string Password {get;set;}
}

Now your code that looks something like this will work:

UserCredentials userCredentials = .... //Get the credentials from somewhere

var context = new ValidationContext(userCredentials);
var results = new List<ValidationResult>();

var isValid = Validator.TryValidateObject(userCredentials, context, results, true);

Also, if you want to share this code with the user class, you could make use of inheritance:

public class User : UserCredentials
{
[Required]
public string FirstName{ get; set; }

[Required]
public string LastName { get; set; }
}

Is there a good reference for data annotations in regards to how DataType works?

I couldn't find much on the web about DataType.PhoneNumber, but I did find this:

http://forums.asp.net/p/1370546/2863383.aspx

In the RTM release, the
DataType.EmailAddress is only used to
mark the type of data for your own
use.

I wanted to find out a bit more, so I pulled out Red Gate's .NET Reflector and started digging around.

Looking at the DataTypeAttribute class, Joseph Daigle is spot on -- each DataType attribute doesn't do any validation; is always returns true (i.e. "valid"). On some data types, some custom display string formatting is done. Phone numbers, however, are pretty much left untouched.

So, I looked into possible solutions to this problem. From what I've found, this looks like the best:

public class EvenNumberAttribute : ValidationAttribute
{
public EvenNumberAttribute() : base(() => Resource1.EvenNumberError) { }
public EvenNumberAttribute(string errorMessage) : base(() => errorMessage) { }

protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value == null)
{
return ValidationResult.Success;
}

int convertedValue;
try
{
convertedValue = Convert.ToInt32(value);
}
catch (FormatException)
{
return new ValidationResult(Resource1.ConversionError);
}

if (convertedValue % 2 == 0)
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
}

Of course, that validates whether a number is odd or even. You could write a custom validation attribute for PhoneNumber, Email, etc. that actually does validation.

How can I validate nested model?

I had the same problem. I ended doing this:

public ActionResult GetTitles(Model model)
{
if(ModelState.IsValid && TryValidateModel(model.NestedModel, "NestedModel."))
{
//Submodel will be validated here.
}
}

WebApi data annotation validation not occuring for complex type parameter properties

Instead of using fields you need to convert them to properties for this to work:

[Required, MaxLength(50)]
public string ExternalId { get; set; }

[Required, MaxLength(50)]
public string Username { get; set; }

Do this for all of your public fields.

Did you look at the object using the debugger and see if the fields were being set? They probably were, but see here for some detail on modelbinding:

ASP.net MVC - Model binding excludes class fields

Update:

I have tried and tested this and am sure it will fix your issue.



Related Topics



Leave a reply



Submit