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.
Recursive validation using annotations and IValidatableObject
See this answer: https://stackoverflow.com/a/3400627/724944
So, there is an error in your class' atributes, and therefore Validate method doesn't get called.
I suggest using CustomValidationAttribute like this:
[CustomValidation(typeof(Customer), "ValidateRelatedObject")]
public CustomerDetails Details
{
get;
private set;
}
public static ValidationResult ValidateRelatedObject(object value, ValidationContext context)
{
var context = new ValidationContext(value, validationContext.ServiceContainer, validationContext.Items);
var results = new List<ValidationResult>();
Validator.TryValidateObject(value, context, results);
// TODO: Wrap or parse multiple ValidationResult's into one ValidationResult
return result;
}
How to validate model in .Net Core 3.1 in console app when the model contains another class's object as a property using DataAnnotations?
In brief, this doesn't work because TryValidateObject
doesn't support the validation of complex nested types. This was also an issue for the .NET Framework version of this type, and @reustmd created a NuGet Package to solve the problem. Unfortunately, it doesn't support .NET Core, but I found there is a package forked from this which does. From the readme:
Installation
Available as NuGet-Package
dataannotationsvalidator
:Install-Package dataannotationsvalidator
Usage
See file
DataAnnotationsValidator/DataAnnotationsValidator.Tests/DataAnnotationsValidatorTests.cs
Short example:
var validator = new DataAnnotationsValidator.DataAnnotationsValidator();
var validationResults = new List<ValidationResult>();
validator.TryValidateObjectRecursive(modelToValidate, validationResults);
This should solve your problem.
As an aside, I also found this proposal discussing adding this functionality to .NET. If you're interested in a technical design discussion of that proposal, there is a video posted by the .NET Foundation discussing it.
Update
For your scenario above, I've installed the above package, changed the VisitRequest
class to make the provider required
public class VisitRequest
{
[Required]
public ProviderInfo Provider { get; set; }
[Required]
[MaxLength(64)]
public string PayerId { get; set; }
}
and then ran the following:
var jsonString = @"{
""ExternalVisitID"": ""123456789"",
""EVVMSID"": ""100""
}";
var modelToValidate = JsonConvert.DeserializeObject<VisitRequest>(jsonString);
var validator = new DataAnnotationsValidator.DataAnnotationsValidator();
var validationResults = new List<ValidationResult>();
validator.TryValidateObjectRecursive(modelToValidate, validationResults);
foreach (var item in validationResults)
{
Console.WriteLine(item.ErrorMessage);
foreach (var memberName in item.MemberNames)
{
Console.WriteLine($"\t{memberName}");
}
}
which produces output of:
The Provider field is required.
Provider
The PayerId field is required.
PayerId
If you're wanting to list each field of a provider that's missing individually, even though that part of the JSON is empty, I think you'll have to fork the NuGet package and make those changes. But this is probably a reason why .NET hasn't supported this functionality out of the box, because people have different expectations for how it should behave.
Validating DataAnnotations with Validator class
I found the answer here: http://forums.silverlight.net/forums/p/149264/377212.aspx
MVC recognizes the MetaDataType attribute, but other projects do not. Before validating, you need to manually register the metadata class:
TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Persona), typeof(Persona_Validation)), typeof(Persona));
ValidationContext context = new ValidationContext(p, null, null);
List<ValidationResult> results = new List<ValidationResult>();
bool valid = Validator.TryValidateObject(p, context, results, true);
Validating a collection with Dataannotations
As it seems, your grade value is not simple int, it is int with some domain specific restrictions. Following that logic, I would design new class for holding grade value
public class GradeValue
{
[Range(100, 200)]
public int Value { get; set; }
}
And in your model, property that describes list of grade values, would be
public List<GradeValue> GradeValues { get; set; }
And the validation will apply rande to every GradeValue in your viewModel. And in view, you do not have to change any single line of code. Moreover, you could design your GradeValue class
to be implicitely convertible to int and vice versa, for simpler usage.
Related Topics
How to Extend C# Built-In Types, Like String
C# Working with Entity Framework in a Multi Threaded Server
Async Threadsafe Get from Memorycache
Long String Interpolation Lines in C#6
How to Print <Xml Version="1.0"> Using Xdocument
C# Quickest Way to Shift Array
How to Get the Available Wifi Aps and Their Signal Strength in .Net
How to Tryparse for Enum Value
Html.Enumdropdownlistfor: Showing a Default Text
Adding and Removing Anonymous Event Handler
From Excel to Datatable in C# with Open Xml
How to Automate Sap Gui with C#
.Net (Dotnet) Wrappers for Opencv
Do Interfaces Derive from System.Object? C# Spec Says Yes, Eric Says No, Reality Says No
Convert an Enum to Another Type of Enum
How to Get the Development/Staging/Production Hosting Environment in Configureservices