I have an View Model that is an IValidatableObject that contains a collection of CustomFields that are also IValidatableObject. Both the view model and the custom fields have custom logic to check if they are valid when posted.
The View Model looks like this:
public class ViewModel : IValidatableObject
{
public bool IsInspected { get; set; }
public DateTime? InspectedDate { get; set; }
public IList<CustomField> CustomFields { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsInspected == true && InspectedDate == null)
{
yield return new ValidationResult("Inspection Date is required if Inspected.", new[] { nameof(InspectedDate) });
}
}
}
public class CustomField : IValidatableObject
{
public bool IsRequired { get; set; }
public string Value { get; set; }
public string DisplayName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsRequired && string.IsNullOrWhiteSpace(Value))
{
yield return new ValidationResult($"The {DisplayName} field is required.", new[] { nameof(Value) });
}
}
}
This correctly validates the CustomFields, but it doesn't validate the ViewModel itself unless the CustomFields have no errors. This means if the user posts invalid Custom Fields and invalid View Model fields, they aren't given notification of the View Model fields until they correct the Custom Fields, post again, and then get the View Model fields validated.
I tried removing IValidatableObject from CustomField and instead doing a foreach loop inside the ViewModel.Validate() method, but that didn't correctly assign ModelState Keys that highlight the input fields on the form. For example the method shown above creates a ModelState Key of CustomFields[0].Value while doing a loop inside ViewModel just creates a Key of Value.
Related
I have 4 string properties, each property for each textbox in my Asp.Net MVC view.
I also have a property that concats the values of each into one:
public string ModelCode {get{return ProjNr+SerialNr+UserNr+ClientNr}}
So I want, if one of the properties doesn't fit the model requirements to get just one error message. Like when the user doesn't input the ClientNr instead of getting an error saying "Client required" it should show an error saying that the ModelCode doesn't meet the requirements.
EDIT:
ProjNr, SerialNr, UserNr and ClientNr are required and they are strings.
You can implement IValidatableObject by your model class
public class YourModel : IValidatableObject
{
public string ProjNr { get; set; }
public string SerialNr { get; set; }
public string UserNr { get; set; }
public string ClientNr { get; set; }
public string ModelCode => $"{ProjNr}{SerialNr}{UserNr}{ClientNr}";
// ...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (/*check if ProjNr or other fields not valid*/)
yield return new ValidationResult(
"ModelCode doesn't meet the requirements",
new [] {"ModelCode"}); // return only ModelCode member
}
}
Another option (if you want to rely on DataAnnotation attributes for validation of ProjNr, SerailNr, UserNr and ClientNr instead of validating them manually - you can check validation errors of those properties in controller and add new validation error if any errors found:
var hasModelCodeErrors = ModelState["ProjNr"].Errors.Any()
|| ModelState["SerialNr"].Errors.Any()
|| ModelState["SerialNr"].Errors.Any()
|| ModelState["ClientNr"].Errors.Any();
if (hasModelCodeErrors)
ModelState.AddModelError("ModelCode", "ModelCode doesn't meet the requirements");
I am using IValidatableObject validation for entities with e.g. following code:
public class OuterObj : IValidatableObject
{
public int ID { get; set; }
public string Name { get; set; }
public IEnumerable<InnerObj> InnerObjList { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this.ID <= 0)
{
yield return new ValidationResult("", new[] { nameof(this.ID) });
}
}
}
public class InnerObj : IValidatableObject
{
public int ID { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this.ID <= 0)
{
yield return new ValidationResult("", new[] { nameof(this.ID) });
}
}
}
In this case when I am validating the outerObj, when there are innerObj present it validates only the innerobj and not the outerobj. It doesn't reach the outerobj validate method in case of presence of innerobj.
I would like to validate both when innerobj present. Please help me with how its done. Why does it not validate the outerobj?
MVC 5.2.3
If the parent class has properties with validation attributes, and any of those properties have been evaluated as invalid, then the IValidatableObject.Validate implementation will not be invoked on the parent class.
This seems to be some sort of short-cutting that MVC is performing for model validation.
Your example does not show validation attribute(s) in the parent class - I'm assuming they were left out.
The workaround is remove validation attributes on properties in the parent class, and only implement validation in the parent class through the IValidatableObject interface.
I have a model with an Entity Framework object on it. The EF object implements IValidatableObject and has a Validate() method on it.
For some reason the method runs twice, so I get two identical model errors on my page.
Any idea why this happens or how to stop it?
I tried adding an _isValidated private member variable but it appears to be resetting to false every time it runs so it must be creating and validating two instances of the model.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(CatName))
{
yield return new ValidationResult("Bad kitty", new string[] { "CatName", "CatName" });
}
}
Edit: My model:
public class KittyModel
{
public Cat Cat { get; set; }
public int? SomeId { get; set; }
public string SomeString { get; set; }
}
Then Cat is just an EF object
[MetadataType(typeof(CatMetadata))]
public partial class Cat : IValidatableObject
{
public sealed class CatMetadata
{
[Required]
public int? CatTypeID { get; set; }
}
// Some other get; only properties here
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(CatName))
{
yield return new ValidationResult("Bad kitty", new string[] { "CatName", "CatName" });
}
}
}
I ran into the same problem today... and I believe this is the reason that Validation Method is called 2 time, from here:
If your model is a complex model inside of a complex model, validation
might be called twice for model-level validators (which
IValidatableObject is considered to be). That's because it's validated
once as a stand-alone object, and then again as the property of its
containing object.
Is there any way of using data annotations to compare two form field (eg. to confirm an email address) are the same, before allowing the form to be posted?
eg. can the regular expression data annotation use the match function to reference another property in a ViewModel?
Use the CompareAttribute
public string EmailAddress {get; set;}
[Compare(nameof(EmailAddress), ErrorMessage = "Emails mismatch")]
public string VerifiedEmailAddress { get; set; }
As one possibe option self-validation:
Implement an interface IValidatableObject with method Validate, where you can put your validation code.
public class TestModel : IValidatableObject
{
public string Email{ get; set; }
public string ConfirmEmail { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Email != ConfirmEmail)
{
yield return new ValidationResult("Emails mismatch", new [] { "ConfirmEmail" });
}
}
}
Please notice: this is only server-side validation.
I have a Model with 4 properties which are of type string. I know you can validate the length of a single property by using the StringLength annotation. However I want to validate the length of the 4 properties combined.
What is the MVC way to do this with data annotation?
I'm asking this because I'm new to MVC and want to do it the correct way before making my own solution.
You could write a custom validation attribute:
public class CombinedMinLengthAttribute: ValidationAttribute
{
public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
{
this.PropertyNames = propertyNames;
this.MinLength = minLength;
}
public string[] PropertyNames { get; private set; }
public int MinLength { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
if (totalLength < this.MinLength)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
and then you might have a view model and decorate one of its properties with it:
public class MyViewModel
{
[CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
public string Foo { get; set; }
public string Bar { get; set; }
public string Baz { get; set; }
}
Self validated model
Your model should implement an interface IValidatableObject. Put your validation code in Validate method:
public class MyModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("*", new [] { nameof(Title) });
if (Description == null)
yield return new ValidationResult("*", new [] { nameof(Description) });
}
}
Please notice: this is a server-side validation. It doesn't work on client-side. You validation will be performed only after form submission.
ExpressiveAnnotations gives you such a possibility:
[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
To improve Darin's answer, it can be bit shorter:
public class UniqueFileName : ValidationAttribute
{
private readonly NewsService _newsService = new NewsService();
public override bool IsValid(object value)
{
if (value == null) { return false; }
var file = (HttpPostedFile) value;
return _newsService.IsFileNameUnique(file.FileName);
}
}
Model:
[UniqueFileName(ErrorMessage = "This file name is not unique.")]
Do note that an error message is required, otherwise the error will be empty.
Background:
Model validations are required for ensuring that the received data we receive is valid and correct so that we can do the further processing with this data. We can validate a model in an action method. The built-in validation attributes are Compare, Range, RegularExpression, Required, StringLength. However we may have scenarios wherein we required validation attributes other than the built-in ones.
Custom Validation Attributes
public class EmployeeModel
{
[Required]
[UniqueEmailAddress]
public string EmailAddress {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int OrganizationId {get;set;}
}
To create a custom validation attribute, you will have to derive this class from ValidationAttribute.
public class UniqueEmailAddress : ValidationAttribute
{
private IEmployeeRepository _employeeRepository;
[Inject]
public IEmployeeRepository EmployeeRepository
{
get { return _employeeRepository; }
set
{
_employeeRepository = value;
}
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var model = (EmployeeModel)validationContext.ObjectInstance;
if(model.Field1 == null){
return new ValidationResult("Field1 is null");
}
if(model.Field2 == null){
return new ValidationResult("Field2 is null");
}
if(model.Field3 == null){
return new ValidationResult("Field3 is null");
}
return ValidationResult.Success;
}
}
Hope this helps. Cheers !
References
Code Project - Custom Validation Attribute in ASP.NET MVC3
Haacked - ASP.NET MVC 2 Custom Validation
A bit late to answer, but for who is searching.
You can easily do this by using an extra property with the data annotation:
public string foo { get; set; }
public string bar { get; set; }
[MinLength(20, ErrorMessage = "too short")]
public string foobar
{
get
{
return foo + bar;
}
}
That's all that is too it really. If you really want to display in a specific place the validation error as well, you can add this in your view:
#Html.ValidationMessage("foobar", "your combined text is too short")
doing this in the view can come in handy if you want to do localization.
Hope this helps!