How to access ModelState.AddModelError() from IsValid function of ValidationAttribute class - asp.net-mvc

i try to add this line ModelState.AddModelError(string key, string errorMessage); in IsValid function of ValidationAttribute class but fail.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class AtleastOneAttribute : ValidationAttribute, IClientValidatable
{
// For Server side
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var oHobby=value as IEnumerable;
foreach (var _object in oHobby)
{
Hobby _oHobby = (Hobby)_object;
if (_oHobby.IsSelected)
{
return ValidationResult.Success;
}
}
}
ModelState.AddModelError("Hobbies", "Err message....");
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
tell me how to access ModelState.AddModelError from IsValid function of ValidationAttribute class ?
thanks

ModelState is not accessible in IsValid function. IsValid method adds an error to ModelState through it's return statement, so if you need to add an error just return it:
return new ValidationResult("Err message....");

Related

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
}

Access Controller context in custom ValidationAttribute

I'm implementing a custom validator by using a ValidationAttribute:
public class CustomAttribute : ValidationAttribute
{
protected override ValidationResult IsValid (object value, ValidationContext validationContext)
{
//...
}
}
But to run the validation I need access to a variable which is part of the application's base controller (it's related to the user currently logged in). How can I get my hands on it?
If you can populate a model property with the variable you can do this:
Model:
[CustomAttribute("MyVariableAsModelProperty", "Failed Validation!")
public string ValidateThis{get; set;}
public string MyVariableAsModelProperty{get; set;}
Your custom validator:
public class CustomAttribute : ValidationAttribute
{
string otherPropertyName;
public CustomAttribute(string otherPropertyName, string errorMessage)
: base(errorMessage)
{
this.otherPropertyName = otherPropertyName;
}
protected override ValidationResult IsValid (object value, ValidationContext validationContext)
{
var otherPropertyInfo = validationContext.ObjectType.GetProperty(this.otherPropertyName);
var referenceProperty = (string)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
//...
}
Now you have the 2 values to compare: value is the model property to be validated, and referenceProperty is the variable.

Access ModelState from Custom Validator

How can I access ModelState from custom validator for adding errors?
class CustomValidator : ValidationAttribute
{
public override bool IsValid(object value)
{
//access modelstate
}
}
Well the bool IsValid method will just add an error in ModelState when returning false. You don't have to manage ModelState directly.
If you want a custom message, you can do it on the ctor.
If you want more control, you may override ValidationResult IsValid(
Object value,
ValidationContext validationContext
)
class CustomValidator : ValidationAttribute
{
//custom message in ctor
public CustomValidator() : base("My custom message") {}
public override bool IsValid(object value)
{
return true;
}
//return a overriden ValidationResult
protected override ValidationResult IsValid(
Object value,
ValidationContext validationContext) {
var message = "ohoh";
return new ValidationResult(message);
}
}

MVC Validator.TryValidateObject does not validate custom atrribute, validateAllProperties = true

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!

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