I have a class called User and a property Name
public class User
{
[Required]
public string Name { get; set; }
}
And I want to validate it, and if there are any errors add to the controller's ModelState or instantiate another modelstate...
[HttpPost]
public ActionResult NewUser(UserViewModel userVM)
{
User u = new User();
u.Name = null;
/* something */
// assume userVM is valid
// I want the following to be false because `user.Name` is null
if (ModelState.IsValid)
{
TempData["NewUserCreated"] = "New user created sucessfully";
return RedirectToAction("Index");
}
return View();
}
The attributes works for UserViewModel, but I want to know how to validate a class without posting it to an action.
How can I accomplish that?
You can use Validator to accomplish this.
var context = new ValidationContext(u, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(u, context, validationResults, true);
I made an entry in the Stack Overflow Documentation explaining how to do this:
Validation Context
Any validation needs a context to give some information about what is being validated. This can include various information such as the object to be validated, some properties, the name to display in the error message, etc.
ValidationContext vc = new ValidationContext(objectToValidate); // The simplest form of validation context. It contains only a reference to the object being validated.
Once the context is created, there are multiple ways of doing validation.
Validate an Object and All of its Properties
ICollection<ValidationResult> results = new List<ValidationResult>(); // Will contain the results of the validation
bool isValid = Validator.TryValidateObject(objectToValidate, vc, results, true); // Validates the object and its properties using the previously created context.
// The variable isValid will be true if everything is valid
// The results variable contains the results of the validation
Validate a Property of an Object
ICollection<ValidationResult> results = new List<ValidationResult>(); // Will contain the results of the validation
bool isValid = Validator.TryValidatePropery(objectToValidate.PropertyToValidate, vc, results, true); // Validates the property using the previously created context.
// The variable isValid will be true if everything is valid
// The results variable contains the results of the validation
And More
To learn more about manual validation see:
ValidationContext Class Documentation
Validator Class Documentation
I wrote a wrapper to make this a bit less clunky to work with.
Usage:
var response = SimpleValidator.Validate(model);
var isValid = response.IsValid;
var messages = response.Results;
Or if you only care about checking validity, it's even tighter:
var isValid = SimpleValidator.IsModelValid(model);
Complete source:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Ether.Validation
{
public static class SimpleValidator
{
/// <summary>
/// Validate the model and return a response, which includes any validation messages and an IsValid bit.
/// </summary>
public static ValidationResponse Validate(object model)
{
var results = new List<ValidationResult>();
var context = new ValidationContext(model);
var isValid = Validator.TryValidateObject(model, context, results, true);
return new ValidationResponse()
{
IsValid = isValid,
Results = results
};
}
/// <summary>
/// Validate the model and return a bit indicating whether the model is valid or not.
/// </summary>
public static bool IsModelValid(object model)
{
var response = Validate(model);
return response.IsValid;
}
}
public class ValidationResponse
{
public List<ValidationResult> Results { get; set; }
public bool IsValid { get; set; }
public ValidationResponse()
{
Results = new List<ValidationResult>();
IsValid = false;
}
}
}
Or at this gist: https://gist.github.com/kinetiq/faed1e3b2da4cca922896d1f7cdcc79b
Since the question is asking specifically about ASP.NET MVC, you can use the TryValidateObject inside your Controller action.
Your desired method overload is TryValidateModel(Object)
Validates the specified model instance.
Returns true if the model validation is successful; otherwise false.
Your modified source code
[HttpPost]
public ActionResult NewUser(UserViewModel userVM)
{
User u = new User();
u.Name = null;
if (this.TryValidateObject(u))
{
TempData["NewUserCreated"] = "New user created sucessfully";
return RedirectToAction("Index");
}
return View();
}
There is another approach to validation, which is more easy reusable - FluentValidation
This library allows to play with inheritance, different rule sets for one model and has many other cool features. Read about advantages here.
With this library definition of validation rules is separated from model and code will look next way:
public class UserValidator:AbstractValidator<User>
{
public UserValidator()
{
RuleFor(x => x.Name).NotEmpty();
}
}
Usage will look next way:
var validator = new UserValidator();
var validationResult = await validator.ValidateAsync(model);
Related
I have a simple scenario using the Entity Framework in C#. I have an Entity Post:
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
In my PostManager I have these methods:
public int AddPost(string name, string description)
{
var post = new Post() { Name = name, Description = description };
using (var db = new DbContext())
{
var res = db.Posts.Add(post);
res.Validate();
db.SaveChanges();
return res.Id;
}
}
public void UpdatePost(int postId, string newName, string newDescription)
{
using (var db = new DbContext())
{
var data = (from post in db.Posts.AsEnumerable()
where post.Id == postId
select post).FirstOrDefault();
data.Name = newName;
data.Description = newDescription;
data.Validate();
db.SaveChanges();
}
}
The method validate() refers to class:
public static class Validator
{
public static void Validate(this Post post)
{
if ( // some control)
throw new someException();
}
I call the validate method before the savechanges() but after adding the object to the context. What's the best practice to validate data in this simple scenario? It's better validate the arguments instead? What's happen to object post if the validate method throw exception after adding the object to the context?
UPDATE:
I have to throw a custom set of exception depending on data validation error.
I strongly recommend you to (if at all possible) to modify your entity so the setters are private (don't worry, EF can still set them on proxy creation), mark the default constructor as protected (EF can still do lazy loading/proxy creation), and make the only public constructors available check the arguments.
This has several benefits:
You limit the number of places where the state of an entity can be changed, leading to less duplication
You protect your class' invariants. By forcing creation of an entity to go via a constructor, you ensure that it is IMPOSSIBLE for an object of your entity to exist in an invalid or unknown state.
You get higher cohesion. By putting the constraints on data closer to the data itself, it becomes easier to understand and reason about your classes.
You code becomes self-documenting to a higher degree. One never has to wonder "is it OK if I set a negative value on this int property?" if it is impossible to even do it in the first place.
Separation of concerns. Your manager shouldn't know how to validate an entity, this just leads to high coupling. I've seen many managers grow into unmaintainable monsters because they simply do everything. Persisting, loading, validation, error handling, conversion, mapping etc. This is basically the polar opposite of SOLID OOP.
I know it is really popular nowadays to just make all "models" into stupid property bags with getters and setters and only a default constructor because (bad) ORMs have forced us to do this, but this is no longer the case, and there are so many issues with this imo.
Code example:
public class Post
{
protected Post() // this constructor is only for EF proxy creation
{
}
public Post(string name, string description)
{
if (/* validation check, inline or delegate */)
throw new ArgumentException();
Name = name;
Description = description;
}
public int Id { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
}
Then your PostManager code becomes trivial:
using (var db = new DbContext())
{
var post = new Post(name, description); // possibly try-catch here
db.Posts.Add(post);
db.SaveChanges();
return post.Id;
}
If the creation/validation logic is extremely intricate this pattern lends itself very well for refactoring to a factory taking care of the creation.
I would also note that encapsulating data in entities exposing a minimal state-changing API leads to classes that are several orders of magnitude easier to test in isolation, if you care at all about that sort of thing.
As I mentioned in the comments above, you might want to check out .NET System.ComponentModel.DataAnnotations namespace.
Data Annotations (DA) allows you to specify attributes on properties to describe what values are acceptable. It's important to know that DA is completely independent of databases and ORM APIs such as Entity Framework so classes decorated with DA attributes can be used in any tier of your system whether it be the data tier; WCF; ASP.NET MVC or WPF.
In the example below, I define a Muppet class with a series of properties.
Name is required and has a max length of 50.
Scaryness takes an int but it must be in the range of {0...100}.
Email is decorated with an imaginary custom validator for validating strings that should contain an e-mail.
Example:
public class Muppet
{
[Required]
[StringLength(50)]
public string Name {get; set;}
public Color Color {get; set; }
[Range(0,100)]
public int Scaryness {get; set; }
[MyCustomEmailValidator]
public string Email {get;set; }
}
In my project I have to throw customException when i validate the data. It's possible do it using Data Annotations?
Yes you can. To validate this object at any time of your application (regardless of whether it has reached EF or not) just perform this:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
.
.
.
Post post = ... // fill it in
Validator.Validate(post);
public static class Validator
{
public static void Validate(this Post post)
{
// uses the extension method GetValidationErrors defined below
if (post.GetValidationErrors().Any())
{
throw new MyCustomException();
}
}
}
public static class ValidationHelpers
{
public static IEnumerable<ValidationResult> GetValidationErrors(this object obj)
{
var validationResults = new List<ValidationResult>();
var context = new ValidationContext(obj, null, null);
Validator.TryValidateObject(obj, context, validationResults, true);
return validationResults;
}
.
.
.
If you want to get the validation error messages you could use this method:
/// <summary>
/// Gets the validation error messages for column.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns></returns>
public static string GetValidationErrorMessages(this object obj)
{
var error = "";
var errors = obj.GetValidationErrors();
var validationResults = errors as ValidationResult[] ?? errors.ToArray();
if (!validationResults.Any())
{
return error;
}
foreach (var ee in validationResults)
{
foreach (var n in ee.MemberNames)
{
error += ee + "; ";
}
}
return error;
}
The free set of steak knives is that the validation attributes will be detected once the object reaches EF where it will be validated there as well in case you forget or the object is changed since.
I think you should be working with Data Annotation as #Micky says above. Your current approach is validating manually after it has been added.
using System.ComponentModel.DataAnnotations;
// Your class
public class Post
{
[Required]
public int Id { get; set; }
[Required,MaxLength(50)]
public string Name { get; set; }
[Required,MinLength(15),MyCustomCheck] // << Here is your custom validator
public string Description { get; set; }
}
// Your factory methods
public class MyFactory() {
public bool AddPost() {
var post = new Post() { Id = 1, Name = null, Description = "This is my test post"};
try {
using (var db = new DbContext()) {
db.Posts.Add(post);
db.SaveChanges();
return true;
}
} catch(System.Data.Entity.Validation.DbEntityValidationException e) {
Console.WriteLine("Something went wrong....");
} catch(MyCustomException e) {
Console.WriteLine(" a Custom Exception was triggered from a custom data annotation...");
}
return false;
}
}
// The custom attribute
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MyCustomCheckAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value instanceof string) {
throw new MyCustomException("The custom exception was just triggered....")
} else {
return true;
}
}
}
// Your custom exception
public class MyCustomException : Exception() {}
See also:
DbEntityValidationException class: https://msdn.microsoft.com/en-us/library/system.data.entity.validation.dbentityvalidationexception(v=vs.113).aspx
Default data annotations
http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx
Building your custom data annotations (validators):
https://msdn.microsoft.com/en-us/library/cc668224.aspx
I always use two validations:
client side - using jQuery Unobtrusive Validation in combination with Data Annotations
server side validation - and here it depends on application - validation is performed in controller actions or deeper in business logic. Nice place to do it is to override OnSave method in your context and do it there
Remember that you can write custom Data Annotation attributes which can validate whatever you need.
You can modify the code in this way:
public int AddPost(string name, string description)
{
var post = new Post() { Name = name, Description = description };
if(res.Validate())
{
using (var db = new DbContext())
{
var res = db.Posts.Add(post);
db.SaveChanges();
return res.Id;
}
}
else
return -1; //if not success
}
public static bool Validate(this Post post)
{
bool isValid=false;
//validate post and change isValid to true if success
if(isvalid)
return true;
}
else
return false;
}
After adding data to DbContext and before calling SaveChanges() you can call GetValidationErrors() method of DbContext and check its count to quiqckly check if there are any errors. You can further enumerate all of errors and get error details against each of them. I have bundled Error conversion from ICollection to string in GetValidationErrorsString() extension method.
if (db.GetValidationErrors().Count() > 0)
{
var errorString = db.GetValidationErrorsString();
}
public static string GetValidationErrorsString(this DbContext dbContext)
{
var validationErrors = dbContext.GetValidationErrors();
string errorString = string.Empty;
foreach (var error in validationErrors)
{
foreach (var innerError in error.ValidationErrors)
{
errorString += string.Format("Property: {0}, Error: {1}<br/>", innerError.PropertyName, innerError.ErrorMessage);
}
}
return errorString;
}
I am trying to validate nested objects (not models in the MVC senss) using annotations and some custom code.
I found the following post useful
Using Data Annotations Validation Manually and Object Graphs
As suggested in an answer, I've created an extra routine in the container class to validate the nested object. Here's my modified test code
public class Customer : IValidatableObject
{
public Customer()
{
Details = new CustomerDetails();
}
[Required]
[MaxLength(2)]
public string Name
{
get;
set;
}
public CustomerDetails Details
{
get;
private set;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var context = new ValidationContext(this.Details, validationContext.ServiceContainer, validationContext.Items);
var results = new List<ValidationResult>();
Validator.TryValidateObject(this.Details, context, results);
return results;
}
}
However I have problems getting all the validation errors, even when calling TryValidateObject with validateAllProperties set to true.
var context = new ValidationContext(cs, null, null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(cs, context, results,true);
If there are any errors in the container, only these will show. Only when there are no errors in the container object, errors in the nested object will show. I suspect it has something to do with the Validate rouine returning a full list, and not being able to add to an (existing) list from the container(?)
Are there any modifications I can make to routine to get all errors to show?
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;
}
I am using the RegularExpressionAttribute from DataAnnotations for validation and would like to test my regex. Is there a way to invoke the attribute directly in a unit test?
I would like to be able to do something similar to this:
public class Person
{
[RegularExpression(#"^[0-9]{3}-[0-9]{3}-[0-9]{4}$")]
public string PhoneNumber { get; set; }
}
Then in a unit test:
[TestMethod]
public void PhoneNumberIsValid
{
var dude = new Person();
dude.PhoneNumber = "555-867-5309";
Assert.IsTrue(dude.IsValid);
}
Or even
Assert.IsTrue(dude.PhoneNumber.IsValid);
I ended up using the static Validator class from the DataAnnotations namespace. My test now looks like this:
[TestMethod]
public void PhoneNumberIsValid()
{
var dude = new Person();
dude.PhoneNumber = "666-978-6410";
var result = Validator.TryValidateObject(dude, new ValidationContext(dude, null, null), null, true);
Assert.IsTrue(result);
}
Just new up a RegularExpressionAttribute object.
var regularExpressionAttribute = new RegularExpressionAttribute("pattern");
Assert.IsTrue(regularExpressionAttribute.IsValid(objToTest));
Sorry for answering late.
I'm new here. If you want test every ValidationAttribute in isolate you can proceed to the next manner for example:
[Test]
public void Test_the_State_value_IsRequired()
{
string value = "Finished";
var propertyInfo = typeof(TimeoffTemporalIncapacityEntry).GetProperty("State");
var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), true).Cast<RequiredAttribute>().FirstOrDefault();
Assert.IsTrue(attribute.IsValid(value));
}
I used the #Martin 's suggestion along with a static constants file which allowed me to avoid specifing the regex string locally
[TestMethod]
public void Test_Regex_NationalinsuranceNumber()
{
var regularExpressionAttribute = new RegularExpressionAttribute(Constants.Regex_NationalInsuranceNumber_Validate);
List<string> validNINumbers = new List<string>() { "TN311258F", "QQ123456A" };
List<string> invalidNINumbers = new List<string>() { "cake", "1234", "TS184LZ" };
validNINumbers.ForEach(p => Assert.IsTrue(regularExpressionAttribute.IsValid(p)));
invalidNINumbers.ForEach(p => Assert.IsFalse(regularExpressionAttribute.IsValid(p)));
}
You can use this class for validate any ValidationAttribute type in isolate:
T = class type containing the property,
A = type ValidationAttribute
Example:
string stateValue = "Pendiente";
ValidationAttributeValidator<ConfirmationTemporalIncapacityEntry, RequiredAttribute> validator =
new ValidationAttributeValidator<ConfirmationTemporalIncapacityEntry, RequiredAttribute>();
Assert.IsTrue(validator.ValidateValidationAttribute("State", stateValue));
public class ValidationAttributeValidator<T,A>
{
public ValidationAttributeValidator() { }
public bool ValidateValidationAttribute(string property, object value)
{
var propertyInfo = typeof(T).GetProperty(property);
var validationAttributes = propertyInfo.GetCustomAttributes(true);
if (validationAttributes == null)
{
return false;
}
List<ValidationAttribute> validationAttributeList = new List<ValidationAttribute>();
foreach (object attribute in validationAttributes)
{
if (attribute.GetType() == typeof(A))
{
validationAttributeList.Add((ValidationAttribute)attribute);
}
}
return(validationAttributeList.Exists(x => x.IsValid(value)));
}
}
Building on #Evelio's answer I am going to provide an answer to how do you unit test custom validators since this doesn't seem to be articulated anywhere on the internet and this is one of the top hits that come up when searching for how to do it.
#Evelio's answer is very close, but it could do with a bit more of an explanation.
To test your validation you need to have a class that attaches validation attributes to its member data. Here I am using a new custom validator that makes sense for my project called FeeTimeUnitValidator. This validator takes a range and another attribute as input. If the other attribute is zero, then the attribute the validator is attached to doesn't matter. But if the other attribute is not zero, then this attribute needs to be in the range.
Here is the MockClass I use for testing:
class MockClass
{
public decimal Fee { get; set; }
[FeeTimeUnitValidator(otherPropertyName:"Fee", minValue:1, maxValue:12)]
public int attributeUnderTest { get; set; }
public int badOtherProperty { get; set; }
[FeeTimeUnitValidator(otherPropertyName: "badOtherProperty", minValue: 1, maxValue: 12)]
public int badAttributeUnderTest { get; set; }
[FeeTimeUnitValidator(otherPropertyName: "NotFoundAttribute", minValue: 1, maxValue: 12)]
public int nameNotFoundAttribute { get; set; }
}
Notice the attribute validation:
[FeeTimeUnitValidator(otherPropertyName:"Fee", minValue:1, maxValue:12)]
This says to check the property "Fee" as the Fee property (i.e., it has to be non-zero) and then the range is 1 - 12.
I instantiate class in the unit test class and set it up with a setup method. Since there are three attributes on this class that have the validator, I pass in the name of the attribute into the setup class.
private MockClass classUnderTest;
private ValidationContext context;
FeeTimeUnitValidator setup(string attributeUnderTest)
{
classUnderTest = new MockClass();
classUnderTest.Fee = 0;
var propertyInfo = typeof(MockClass).GetProperty(attributeUnderTest);
var validatorArray = propertyInfo.GetCustomAttributes(typeof(FeeTimeUnitValidator), true);
Assert.AreEqual(1, validatorArray.Length);
var validator = validatorArray[0];
Assert.IsTrue(validator.GetType().Equals(typeof(FeeTimeUnitValidator)));
context = new ValidationContext(classUnderTest, null, null);
return (FeeTimeUnitValidator)validator;
}
There are a few things of interest. I am using #Evelio's approach to extract the validator from the attribute. This is doe in lines 3 and 4 of the setup routine. Then, since this is a unit test method, I do some asserts to make sure that I got what I expected. This actually caught a problem when I transferred this pattern to another unit test class for another validator.
Then the other key is that I create the ValidationContext (since the more complicated validators need a context to find the other attributes they refer to - in my case I use it to find the Fee attribute). When I was researching how to unit test these custom validators, what was tripping me up was the ValidationContext. I couldn't find any information about how to create them. I believe the "context" for the attribute validation is the class in which the attribute lives. This is why I create the validation context with the class instance as the first parameter. This then provides the validator with access to the other attributes on the class so you can do cross attribute validation.
Now that i have the context created and a pointer to a validator, I can jump into the unit test itself to ensure that the validator is doing its job properly:
[TestMethod]
public void TestInRangeIsValidWhenFeeNonZero()
{
// Arrange
var validator = setup("attributeUnderTest");
classUnderTest.Fee = 10;
// Act
ValidationResult value12 = validator.GetValidationResult(12, context);
ValidationResult value1 = validator.GetValidationResult(1, context);
ValidationResult value5 = validator.GetValidationResult(5, context);
// Assert
Assert.AreEqual(ValidationResult.Success, value12);
Assert.AreEqual(ValidationResult.Success, value1);
Assert.AreEqual(ValidationResult.Success, value5);
}
If my validator didn't need a context (i.e., it could validate the attribute without reference to the other attributes), then I could use the simpler interface of IsValid(), but if the validator needs a non-null context, you have to use the GetValidationResult() method like I have done here.
I hope this helps somebody else who might be writing validators and is as religious about unit testing as I am. :)
Here is a good article on creating custom validators.
Extending on #CobraGeek's answer and #Erik's comment, you can use the Validator.TryValidateProperty to validate only that one field instead of the whole object, as so:
var results = new List<ValidationResult>();
Person dude = new Person();
System.ComponentModel.TypeDescriptor.AddProviderTransparent
(new AssociatedMetadataTypeTypeDescriptionProvider(dude.GetType()), dude.GetType());
dude.PhoneNumber = "555-867-5309";
var vc = new ValidationContext(dude, null, null);
vc.MemberName = "PhoneNumber";
bool result = Validator.TryValidateProperty(dude.PhoneNumber, vc, results);
After which result is the boolean indicating success of the validation, and if false results contains the list of the details of the errors thrown.
// You can do something like this.
[TestMethod]
public void PhoneNumberIsValid
{
var propInfo = typeof(Person).GetProperty("PhoneNumber");
var attr = propInfo.GetCustomAttributes(typeof(RegularExpressionAttribute), true);
// Act Assert Positives
Assert.IsTrue(((RegularExpressionAttribute)attr [0]).IsValid("555-55-5555"));
// Act Assert Negative
Assert.IsFalse(((RegularExpressionAttribute)attr[0]).IsValid("123654654654"));
}
I have a custom asp.net mvc class validation attribute.
My question is how can I unit test it?
It would be one thing to test that the class has the attribute but this would not actually test that the logic inside it. This is what I want to test.
[Serializable]
[EligabilityStudentDebtsAttribute(ErrorMessage = "You must answer yes or no to all questions")]
public class Eligability
{
[BooleanRequiredToBeTrue(ErrorMessage = "You must agree to the statements listed")]
public bool StatementAgree { get; set; }
[Required(ErrorMessage = "Please choose an option")]
public bool? Income { get; set; }
.....removed for brevity
}
[AttributeUsage(AttributeTargets.Class)]
public class EligabilityStudentDebtsAttribute : ValidationAttribute
{
// If AnyDebts is true then
// StudentDebts must be true or false
public override bool IsValid(object value)
{
Eligability elig = (Eligability)value;
bool ok = true;
if (elig.AnyDebts == true)
{
if (elig.StudentDebts == null)
{
ok = false;
}
}
return ok;
}
}
I have tried to write a test as follows but this does not work:
[TestMethod]
public void Eligability_model_StudentDebts_is_required_if_AnyDebts_is_true()
{
// Arrange
var eligability = new Eligability();
var controller = new ApplicationController();
// Act
controller.ModelState.Clear();
controller.ValidateModel(eligability);
var actionResult = controller.Section2(eligability,null,string.Empty);
// Assert
Assert.IsInstanceOfType(actionResult, typeof(ViewResult));
Assert.AreEqual(string.Empty, ((ViewResult)actionResult).ViewName);
Assert.AreEqual(eligability, ((ViewResult)actionResult).ViewData.Model);
Assert.IsFalse(((ViewResult)actionResult).ViewData.ModelState.IsValid);
}
The ModelStateDictionary does not contain the key for this custom attribute.
It only contains the attributes for the standard validation attributes.
Why is this?
What is the best way to test these custom attributes?
Your attribute EligabilityStudentDebtsAttribute is just a standard class, like everything else, just unit test the IsValid() method. If it works OK, trust to Framework that attribute works OK.
So:
[Test]
public void AttibuteTest()
{
// arrange
var value = //.. value to test - new Eligability() ;
var attrib = new EligabilityStudentDebtsAttribute();
// act
var result = attrib.IsValid(value);
// assert
Assert.That(result, Is.True)
}
Your custom validation attribute might be dependent on the state of other properties. In this case you can use the System.ComponentModel.DataAnnotations.Validator static methods, for example:
var model = ...
var context = new ValidationContext(model);
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(model, context, results, true);
Assert.True(isValid);
I have found out that IsValid does not work well on simple types like string. E.g. if you have a validation on a string query parameter that is not an object. Additionally it's easier to test a value directly on the attribute without having to provide a whole object. It also allows to check the error message. This is how it works:
string input = "myteststring";
var myAttribute = new MyAttribute()
var result = attribute.GetValidationResult(input, new ValidationContext(input));
var isSuccess = result == ValidationResult.Success;
var errorMessage = result?.ErrorMessage;
This code tests only the validation of your input value and nothing else.
P.S. I have tested this in dotnet core, but I would think this works for ordinary dotnet as well.
Can I automatically validate complex child objects when validating a parent object and include the results in the populated ICollection<ValidationResult>?
If I run the following code:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ConsoleApplication1
{
public class Person
{
[Required]
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
[Required]
public string Street { get; set; }
[Required]
public string City { get; set; }
[Required]
public string State { get; set; }
}
class Program
{
static void Main(string[] args)
{
Person person = new Person
{
Name = null,
Address = new Address
{
Street = "123 Any St",
City = "New York",
State = null
}
};
var validationContext = new ValidationContext(person, null, null);
var validationResults = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(person, validationContext, validationResults);
Console.WriteLine(isValid);
validationResults.ForEach(r => Console.WriteLine(r.ErrorMessage));
Console.ReadKey(true);
}
}
}
I get the following output:
False
The Name field is required.
But I was expecting something similar to:
False
The Name field is required.
The State field is required.
I offered a bounty for a better child object validation solution but didn't get any takers, ideally
validating child objects to an arbitrary depth
handling multiple errors per object
correctly identifying the validation errors on the child object fields.
I'm still surprised the framework doesn't support this.
Issue - Model Binder Order
This is, unfortunately, the standard behavior of Validator.TryValidateObject which
does not recursively validate the property values of the object
As pointed out in Jeff Handley's article on Validating Object and Properties with the Validator, by default, the validator will validate in order:
Property-Level Attributes
Object-Level Attributes
Model-Level implementation IValidatableObject
The problem is, at each step of the way...
If any validators are invalid, Validator.ValidateObject will abort validation and return the failure(s)
Issue - Model Binder Fields
Another possible issue is that the model binder will only run validation on objects that it has decided to bind. For example, if you don't provide inputs for fields within complex types on your model, the model binder won't need to check those properties at all because it hasn't called the constructor on those objects. According to Brad Wilson's great article on Input Validation vs. Model Validation in ASP.NET MVC:
The reason we don't "dive" into the Address object recursively is that there was nothing in the form that bound any values inside of Address.
Solution - Validate Object at the same time as Properties
One way to solve this problem is to convert object-level validations to property level validation by adding a custom validation attribute to the property that will return with the validation result of the object itself.
Josh Carroll's article on Recursive Validation Using DataAnnotations provides an implementation of one such strategy (originally in this SO question). If we want to validate a complex type (like Address), we can add a custom ValidateObject attribute to the property, so it is evaluated on the first step
public class Person {
[Required]
public String Name { get; set; }
[Required, ValidateObject]
public Address Address { get; set; }
}
You'll need to add the following ValidateObjectAttribute implementation:
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);
}
}
Solution - Validate Model at the Same time as Properties
For objects that implement IValidatableObject, when we check the ModelState, we can also check to see if the model itself is valid before returning the list of errors. We can add any errors we want by calling ModelState.AddModelError(field, error). As specified in How to force MVC to Validate IValidatableObject, we can do it like this:
[HttpPost]
public ActionResult Create(Model model) {
if (!ModelState.IsValid) {
var errors = model.Validate(new ValidationContext(model, null, null));
foreach (var error in errors)
foreach (var memberName in error.MemberNames)
ModelState.AddModelError(memberName, error.ErrorMessage);
return View(post);
}
}
Also, if you want a more elegant solution, you can write the code once by providing your own custom model binder implementation in Application_Start() with ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());. There are good implementations here and here
I also ran into this, and found this thread. Here's a first pass:
namespace Foo
{
using System.ComponentModel.DataAnnotations;
using System.Linq;
/// <summary>
/// Attribute class used to validate child properties.
/// </summary>
/// <remarks>
/// See: http://stackoverflow.com/questions/2493800/how-can-i-tell-the-data-annotations-validator-to-also-validate-complex-child-pro
/// Apparently the Data Annotations validator does not validate complex child properties.
/// To do so, slap this attribute on a your property (probably a nested view model)
/// whose type has validation attributes on its properties.
/// This will validate until a nested <see cref="System.ComponentModel.DataAnnotations.ValidationAttribute" />
/// fails. The failed validation result will be returned. In other words, it will fail one at a time.
/// </remarks>
public class HasNestedValidationAttribute : ValidationAttribute
{
/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult"/> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var isValid = true;
var result = ValidationResult.Success;
var nestedValidationProperties = value.GetType().GetProperties()
.Where(p => IsDefined(p, typeof(ValidationAttribute)))
.OrderBy(p => p.Name);//Not the best order, but at least known and repeatable.
foreach (var property in nestedValidationProperties)
{
var validators = GetCustomAttributes(property, typeof(ValidationAttribute)) as ValidationAttribute[];
if (validators == null || validators.Length == 0) continue;
foreach (var validator in validators)
{
var propertyValue = property.GetValue(value, null);
result = validator.GetValidationResult(propertyValue, new ValidationContext(value, null, null));
if (result == ValidationResult.Success) continue;
isValid = false;
break;
}
if (!isValid)
{
break;
}
}
return result;
}
}
}
You will need to make your own validator attribute (eg, [CompositeField]) that validates the child properties.