MVC Validator.TryValidateObject does not validate custom atrribute, validateAllProperties = true - asp.net-mvc

When calling Validator.TryValidateObject with validateAllProperties = true my custom validation attribute does not get triggered. The ValidationResult does not contain an entry for my erroneous property value. Below is the model, attribute and code used to test this.
//Model
public class Model
{
[AmountGreaterThanZero]
public int? Amount { get; set; }
}
//Attribute
public sealed class AmountGreaterThanZero: ValidationAttribute
{
private const string errorMessage = "Amount should be greater than zero.";
public AmountGreaterThanZero() : base(errorMessage) { }
public override string FormatErrorMessage(string name)
{
return errorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
if ((int)value <= 0)
{
var message = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(message);
}
}
return null;
}
public override bool IsValid(object value)
{
if ((int)value < 0)
{
return false;
}
return true;
}
}
//Validation Code
var container = new Container();
container.ModelList = new List<Model>() { new Model() { Amount = -5 } };
var validationContext = new ValidationContext(container, null, null);
var validationResults = new List<ValidationResult>();
var modelIsValid = Validator.TryValidateObject(container, validationContext, validationResults, true);
Note: That the validation works fine and ValidationResult returns with correct error message if I use the TryValidateProperty method.
Edit: As suggested by #Fals and the approach i took was to validate each object in the list individually.

Marking the comment by #Fals as the answer as this is the approach i ended up taking. As there was no other question answered that satisfied my original question.
#Fals - Thats the problem, you must pass object by object to validade!

Related

MVC: Custom attribute not populating in view

I am writing a custom attribute to validate that a first and last name does not exceed a certain amount of characters, but the error message is not displaying like it does for out-of-the-box annotations.
Here is my implementation.
public class User
{
[Required(ErrorMessage = "Last Name is required.")]
[RegularExpression(#"^[a-zA-Z'\s]{1,50}$", ErrorMessage = "Please enter a valid last name.")]
[FullNameMaxLength("FirstName")]
public string LastName { get; set; }
}
public class FullNameMaxLengthAttribute : ValidationAttribute
{
private string _firstName;
public FullNameMaxLengthAttribute(string firstName)
{
_firstName = firstName;
}
protected override ValidationResult IsValid(object lastName, ValidationContext validationContext)
{
clsUserRegistration userRegistrationContext = (clsUserRegistration)validationContext.ObjectInstance;
if (lastName != null)
{
string strValue = lastName.ToString();
PropertyInfo propInfo = validationContext.ObjectType.GetProperty(_firstName);
if (propInfo == null)
return new ValidationResult(String.Format("Property {0} is undefined.", _firstName));
var fieldValue = propInfo.GetValue(validationContext.ObjectInstance, null).ToString();
if (strValue.Length + fieldValue.Length > 53)
{
return new ValidationResult("First and last names are too long!");
}
return ValidationResult.Success;
}
return null;
}
}
In my view, I have a ValidationMessageFor, and it works fine with non-custom attributes. When I step through my model, it returns the ValidationMessage, but I cannot see that error message. Any thoughts?
The above is just the "back-end" validation. This for example does still work when user's browser has JavaScript turned off - the page will post back regardless of errors but then show the form again with validation messages on it.
For "front-end" validation, you need something along these lines:
public class FullNameMaxLengthAttribute : ValidationAttribute, IClientValidatable
{
// Your Properties and IsValid method here
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.DisplayName) : ErrorMessage,
ValidationType = "fullnamemaxlength"
};
rule.ValidationParameters["firstname"] = FirstName;
rule.ValidationParameters["maxlength"] = 53;
yield return rule;
}
}
And then in JavaScript that is added to the page:
if (jQuery.validator) {
jQuery.validator.addMethod("fullnamemaxlength", function(value, element, param) {
var name = param.firstname;
var max = param.maxlength;
return name.length > max;
});
jQuery.validator.unobtrusive.adapters.add("fullnamemaxlength", ["firstname", "maxlength"], function (options) {
options.rules.fullnamemaxlength = {};
options.rules.fullnamemaxlength.firstname = options.params.firstname;
options.rules.fullnamemaxlength.maxlength = options.params.maximum;
options.messages.fullnamemaxlength = options.message;
}
);
}
Note this sits OUTSIDE of document.ready() { };
Something similar here: client-side validation in custom validation attribute - asp.net mvc 4

How can I pass an addtional value to a custom ModelState.IsValid method?

// In a partial class I am trying to create a custom ModelState.IsValid method. So far, it takes an object as a parameter and receives all the values of properties decorated with a validation attribute. This is great but I'd like to pass the method another parameter from the view. Then, I can use the additional value to help determine validity and possibly return a custom message based on the additional value.
Below is the partial class and some IsValid methods I've tried.
[MetadataType(typeof(CS_Parameter_Statewide_AllGrades_ScenarioMetaData))]
public partial class CS_Parameter_Statewide_AllGrades_Scenario
{
public int Category { get; set; }
public class CS_Parameter_Statewide_AllGrades_ScenarioMetaData
{
[FormatAttribute]
public double Amount { get; set; }
}
}
public class FormatAttribute : ValidationAttribute, IClientValidatable // IClientValidatable for client side Validation
{
// this does not work; i don't know how to pass an additional value
public override bool IsValid(object value, int additonalValue)
{
return true;
}
// this is what I would like to do
public override string IsValid(object value, int additonalValue)
{
if (additonalValue == 1)
// validation method 1
// return message 1
else if (additonalValue == 2)
// validation method 2
// return message 2
else
// validation method 3
// return message 3
}
// this is something I was playing with
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var message = "ohoh";
return new ValidationResult(message);
}
// Implement IClientValidatable for client side Validation
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
return new ModelClientValidationRule[] { new ModelClientValidationRule { ValidationType = "dropdown", ErrorMessage = this.ErrorMessage } };
}
}
`
You need a constructor to pass the name of a property whose value will contain the data use to compare and validate. For example
Model
[Format("MyOtherProperty")]
public double Amount { get; set; }
public int MyOtherProperty { get; set; } // the property use to validate
Attribute
public class FormatAttribute : ValidationAttribute
{
private readonly string _otherProperty;
public FormatAttribute(string otherProperty)
{
_otherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_otherProperty);
if (property == null)
{
return new ValidationResult(string.Format("Unknown property: {0}", _otherProperty));
}
object otherValue = property.GetValue(validationContext.ObjectInstance, null);
if (otherValue == someValue) // cast otherValue to correct type
{
return new ValidationResult("some error message");
}
else if (....)
{
return new ValidationResult("another error message");
}
return null;
}
}
You can Create Constructor and provide additional Field name in that constructor
public class FormatAttribute : ValidationAttribute, IClientValidatable // IClientValidatable for client side Validation
{
private string _additionalAttribute;
public FormatAttribute(string additionalAttribut)
{
_additionalAttribute = additionalAttribut
}
}
Then in IsValid function using ValidationContext you can retrieve the value of additional field
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
object additionalFieldValue = GetPropertyValue(validationContext.ObjectInstance, _additionalField);
// Do Logic Here
}

Conditional rangevalidator +MVC

In a model of my ASP.NET MVC application I would like validate a textbox as required only if a specific checkbox is checked.
Something like
public bool retired {get, set};
[RangeIf("retired",20,50)]
public int retirementAge {get, set};
How can I do that?
You need to create your custom validation attribute like this:
public class RangeIfAttribute : ValidationAttribute
{
protected RangeAttribute _innerAttribute;
public string DependentProperty { get; set; }
public RangeIfAttribute(string dependentProperty, int minimum, int maximum)
{
_innerAttribute = new RangeAttribute(minimum, maximum);
DependentProperty = dependentProperty;
}
public RangeIfAttribute(string dependentProperty, double minimum, double maximum)
{
_innerAttribute = new RangeAttribute(minimum, maximum);
DependentProperty = dependentProperty;
}
public RangeIfAttribute(string dependentProperty, Type type, string minimum, string maximum)
{
_innerAttribute = new RangeAttribute(type, minimum, maximum);
DependentProperty = dependentProperty;
}
public override string FormatErrorMessage(string name)
{
return _innerAttribute.FormatErrorMessage(name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(DependentProperty);
if (field != null && field.PropertyType.Equals(typeof(bool)))
{
// get the value of the dependent property
var dependentValue = (bool)(field.GetValue(validationContext.ObjectInstance, null));
// if dependentValue is true...
if (dependentValue)
{
if (!_innerAttribute.IsValid(value))
// validation failed - return an error
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
}
Then, you can use it in your Model just like in your question.

Semi-Complex View Model Property Validation in ASP.NET MVC 3

I am struggling to complete a server-client validation solution for a semi-complex scenario. I have a core type called DateRange:
public class DateRange {
public DateRange (DateTime? start, DateTime? end) { ... }
public DateTime? Start { get; private set; }
public DateTime? End { get; private set; }
}
I have a view model like:
public class MyViewModel {
public DateRange Period { get; set; }
}
I have a %mvcproject%\Views\Shared\EditorTemplates\DateRange.cshtml like:
#model MyCore.DateRange
#Html.Editor("Start", "Date")
#Html.Editor("End", "Date")
I also have a DateRangeModelBinder to bind the two form inputs into the DateRange property. The problem I'm having is with a DateRangeRequiredAttribute:
public class DateRangeRequired : ValidationAttribute, IClientValidatable,
IMetadataAware
{
private const string DefaultErrorMessage =
"{0} is required.";
public DateRangeRequired(bool endIsRequired = true)
: base(() => DefaultErrorMessage)
{
EndIsRequired = endIsRequired;
}
public bool EndIsRequired { get; set; }
public override bool IsValid(object value)
{
if (value == null)
{
return false;
}
if (!value.GetType().IsAssignableFrom(typeof(DateRange)))
{
throw new ArgumentException("Value is not a DateRange.");
}
var dateRange = value as DateRange;
return (dateRange.Start.HasValue && !EndIsRequired) ||
(dateRange.Start.HasValue && dateRange.End.HasValue && EndIsRequired);
}
public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "daterangerequired"
};
rule.ValidationParameters.Add("endisrequired", EndIsRequired.ToString().ToLower());
yield return rule;
}
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.DataTypeName = "DateRange";
}
}
I can't get it to hook up to the two inputs. It's almost like there needs to be a ValidatorTemplate that pairs with the EditorTemplate because of the split inputs. Any ideas? Let me know if additional clarification is needed.
You haven't shown exactly how your custom DateRangeRequiredAttribute implementation looks like, so let me suggest an example:
public class DateRangeRequiredAttribute : ValidationAttribute, IClientValidatable
{
private readonly string _otherProperty;
public DateRangeRequiredAttribute(string otherProperty)
{
_otherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_otherProperty);
if (property == null)
{
return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "Unknown property {0}", _otherProperty));
}
var otherValue = property.GetValue(validationContext.ObjectInstance, null);
if (!(value is DateTime) || !(otherValue is DateTime))
{
return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "The two properties to compare must be of type DateTime"));
}
if ((DateTime)value >= (DateTime)otherValue)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "daterange"
};
rule.ValidationParameters.Add("other", "*." + _otherProperty);
yield return rule;
}
}
then you could decorate your view model with it:
public class DateRange
{
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
[DateRangeRequired("End", ErrorMessage = "Please select a start date before the end date")]
public DateTime? Start { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
[Required]
public DateTime? End { get; set; }
}
and finally in the view register the adapter:
jQuery.validator.unobtrusive.adapters.add(
'daterange', ['other'], function (options) {
var getModelPrefix = function (fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
};
var appendModelPrefix = function (value, prefix) {
if (value.indexOf('*.') === 0) {
value = value.replace('*.', prefix);
}
return value;
};
var prefix = getModelPrefix(options.element.name),
other = options.params.other,
fullOtherName = appendModelPrefix(other, prefix),
element = $(options.form).find(':input[name="' + fullOtherName + '"]')[0];
options.rules['daterange'] = element;
if (options.message) {
options.messages['daterange'] = options.message;
}
}
);
jQuery.validator.addMethod('daterange', function (value, element, params) {
// TODO: some more advanced date checking could be applied here
// currently it uses the current browser culture setting to perform
// the parsing. If you needed to use the server side culture, this code
// could be adapted respectively
var date = new Date(value);
var otherDate = new Date($(params).val());
return date < otherDate;
}, '');
After reading this pornography, you might consider using FluentValidation.NET which renders this extremely simple validation scenario a couple of lines to implement (which is how such simple validation scenarios should be done). I would strongly recommend you this library. I am using it in all my projects because I am sick of DataAnnotations for validation. They are so pretty limited.

Unable to set membernames from custom validation attribute in MVC2

I have created a custom validation attribute by subclassing ValidationAttribute. The attribute is applied to my viewmodel at the class level as it needs to validate more than one property.
I am overriding
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
and returning:
new ValidationResult("Always Fail", new List<string> { "DateOfBirth" });
in all cases where DateOfBirth is one of the properties on my view model.
When I run my application, I can see this getting hit. ModelState.IsValid is set to false correctly but when I inspect the ModelState contents, I see that the Property DateOfBirth does NOT contain any errors. Instead I have an empty string Key with a value of null and an exception containing the string I specified in my validation attribute.
This results in no error message being displayed in my UI when using ValidationMessageFor. If I use ValidationSummary, then I can see the error. This is because it is not associated with a property.
It looks as though it is ignoring the fact that I have specified the membername in the validation result.
Why is this and how do I fix it?
EXAMPLE CODE AS REQUESTED:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class ExampleValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// note that I will be doing complex validation of multiple properties when complete so this is why it is a class level attribute
return new ValidationResult("Always Fail", new List<string> { "DateOfBirth" });
}
}
[ExampleValidation]
public class ExampleViewModel
{
public string DateOfBirth { get; set; }
}
hello everybody.
Still looking for solution?
I've solved the same problem today. You have to create custom validation attribute which will validate 2 dates (example below). Then you need Adapter (validator) which will validate model with your custom attribute. And the last thing is binding adapter with attribute. Maybe some example will explain it better than me :)
Here we go:
DateCompareAttribute.cs:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class DateCompareAttribute : ValidationAttribute
{
public enum Operations
{
Equals,
LesserThan,
GreaterThan,
LesserOrEquals,
GreaterOrEquals,
NotEquals
};
private string _From;
private string _To;
private PropertyInfo _FromPropertyInfo;
private PropertyInfo _ToPropertyInfo;
private Operations _Operation;
public string MemberName
{
get
{
return _From;
}
}
public DateCompareAttribute(string from, string to, Operations operation)
{
_From = from;
_To = to;
_Operation = operation;
//gets the error message for the operation from resource file
ErrorMessageResourceName = "DateCompare" + operation.ToString();
ErrorMessageResourceType = typeof(ValidationStrings);
}
public override bool IsValid(object value)
{
Type type = value.GetType();
_FromPropertyInfo = type.GetProperty(_From);
_ToPropertyInfo = type.GetProperty(_To);
//gets the values of 2 dates from model (using reflection)
DateTime? from = (DateTime?)_FromPropertyInfo.GetValue(value, null);
DateTime? to = (DateTime?)_ToPropertyInfo.GetValue(value, null);
//compare dates
if ((from != null) && (to != null))
{
int result = from.Value.CompareTo(to.Value);
switch (_Operation)
{
case Operations.LesserThan:
return result == -1;
case Operations.LesserOrEquals:
return result <= 0;
case Operations.Equals:
return result == 0;
case Operations.NotEquals:
return result != 0;
case Operations.GreaterOrEquals:
return result >= 0;
case Operations.GreaterThan:
return result == 1;
}
}
return true;
}
public override string FormatErrorMessage(string name)
{
DisplayNameAttribute aFrom = (DisplayNameAttribute)_FromPropertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
DisplayNameAttribute aTo = (DisplayNameAttribute)_ToPropertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
return string.Format(ErrorMessageString,
!string.IsNullOrWhiteSpace(aFrom.DisplayName) ? aFrom.DisplayName : _From,
!string.IsNullOrWhiteSpace(aTo.DisplayName) ? aTo.DisplayName : _To);
}
}
DateCompareAttributeAdapter.cs:
public class DateCompareAttributeAdapter : DataAnnotationsModelValidator<DateCompareAttribute>
{
public DateCompareAttributeAdapter(ModelMetadata metadata, ControllerContext context, DateCompareAttribute attribute)
: base(metadata, context, attribute) {
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
if (!Attribute.IsValid(Metadata.Model))
{
yield return new ModelValidationResult
{
Message = ErrorMessage,
MemberName = Attribute.MemberName
};
}
}
}
Global.asax:
protected void Application_Start()
{
// ...
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(DateCompareAttribute), typeof(DateCompareAttributeAdapter));
}
CustomViewModel.cs:
[DateCompare("StartDateTime", "EndDateTime", DateCompareAttribute.Operations.LesserOrEquals)]
public class CustomViewModel
{
// Properties...
public DateTime? StartDateTime
{
get;
set;
}
public DateTime? EndDateTime
{
get;
set;
}
}
I am not aware of an easy way fix this behavior. That's one of the reasons why I hate data annotations. Doing the same with FluentValidation would be a peace of cake:
public class ExampleViewModelValidator: AbstractValidator<ExampleViewModel>
{
public ExampleViewModelValidator()
{
RuleFor(x => x.EndDate)
.GreaterThan(x => x.StartDate)
.WithMessage("end date must be after start date");
}
}
FluentValidation has great support and integration with ASP.NET MVC.
When returning the validation result use the two parameter constructor.
Pass it an array with the context.MemberName as the only value.
Hope this helps
<AttributeUsage(AttributeTargets.Property Or AttributeTargets.Field, AllowMultiple:=False)>
Public Class NonNegativeAttribute
Inherits ValidationAttribute
Public Sub New()
End Sub
Protected Overrides Function IsValid(num As Object, context As ValidationContext) As ValidationResult
Dim t = num.GetType()
If (t.IsValueType AndAlso Not t.IsAssignableFrom(GetType(String))) Then
If ((num >= 0)) Then
Return ValidationResult.Success
End If
Return New ValidationResult(context.MemberName & " must be a positive number", New String() {context.MemberName})
End If
Throw New ValidationException(t.FullName + " is not a valid type. Must be a number")
End Function
End Class
You need to set the ErrorMessage property, so for example:
public class DOBValidAttribute : ValidationAttribute
{
private static string _errorMessage = "Date of birth is a required field.";
public DOBValidAttribute() : base(_errorMessage)
{
}
//etc......overriding IsValid....

Resources