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
I Want to Understand the Lambda Expression in @Html.Displayfor(Modelitem => Item.Firstname)
Get an Idatareader from a Typed List
Dynamic MySQL Database Connection for Entity Framework 6
Does Mstest Have an Equivalent to Nunit's Testcase
Datacontractserializer Doesn't Call My Constructor
Linq Group by Multiple Fields -Syntax Help
Convert Int to a Bit Array in .Net
Get All Registered Routes in ASP.NET Core
How to Support Listbox Selecteditems Binding with Mvvm in a Navigable Application
Click Through Transparency for Visual C# Window Forms
Entity Framework - Retrieve Id Before 'Savechanges' Inside a Transaction
How to Tell the Data Annotations Validator to Also Validate Complex Child Properties
How to Prevent a Windows from Being Moved
Parse and Modify a Query String in .Net Core
Implicit Conversion Issue in a Ternary Condition
Filter SQL Based on C# List Instead of a Filter Table
Badimageformatexception When Loading 32 Bit Dll, Target Is X86