Unable to set membernames from custom validation attribute in MVC2 - asp.net-mvc

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....

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
}

Asp.net MVC RegularExpression Validator whose error message depends on another property in the Model

I have a model property which is of type double with two decimal points allowed, I have used the following attribute to ensure this
[RegularExpression(RegularExpressionHelper.PositiveTwoDecimalNumberExpression, ErrorMessage = "Invalid Account balance")]
public double AccountBalance { get; set; }
there is another model property say,
public bool IsSavingsAccount { get; set; }
Now the behavior i need is wheneever IsSavingsAccount is true the message for invalid account balance should be "Invalid Savings Account Balance" else it should be "Invalid Current Account Balance".The Other property type happens to be a boolean here but it could be anything so i am looking for a solution which can accommodate this.
Is it necessary to write a custom validator to achieve this ? if yes how to go about it ?
make a custom validation attribute that inherits from RegularExpressionAttribute, and use the validation context to check the other property:
using System.ComponentModel.DataAnnotations;
public class PositiveTwoDecimalAttribute : RegularExpressionAttribute
{
public PositiveTwoDecimalAttribute() : base(RegularExpressionHelper.PositiveTwoDecimalNumberExpression) { }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
bool isSavingsAccount = (bool)validationContext.ObjectType.GetProperty("IsSavingsAccount").GetValue(validationContext.ObjectInstance, null);
var errorMessage = isSavingsAccount ? "Invalid Savings Account balance" : "Invalid Current balance";
if(!base.IsValid(value)){
return new ValidationResult(errorMessage);
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new BalanceAmountModelClientValidationRegexRule(this.ErrorMessage, RegularExpressionHelper.PositiveTwoDecimalNumberExpression);
}
}
public class BalanceAmountModelClientValidationRegexRule: ModelClientValidationRegexRule
{
public BalanceAmountModelClientValidationRegexRule(string errorMessage, string pattern)
: base(errorMessage, pattern)
{
}
}

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!

Data Annotations - How can I replace Range values with Web.Config values in MVC3?

How can I replace the Range values with Web.Config values in MVC3?
[Range(5, 20, ErrorMessage = "Initial Deposit should be between $5.00 and $20.00")
public decimal InitialDeposit { get; set; }
web.config:
<add key="MinBalance" value="5.00"/>
<add key="MaxDeposit" value="20.00"/>
You will need to create a custom attribute inheriting from RangeAttribute and implementing IClientValidatable.
public class ConfigRangeAttribute : RangeAttribute, IClientValidatable
{
public ConfigRangeAttribute(int Int) :
base
(Convert.ToInt32(WebConfigurationManager.AppSettings["IntMin"]),
Convert.ToInt32(WebConfigurationManager.AppSettings["IntMax"])) { }
public ConfigRangeAttribute(double Double) :
base
(Convert.ToDouble(WebConfigurationManager.AppSettings["DoubleMin"]),
Convert.ToDouble(WebConfigurationManager.AppSettings["DoubleMax"]))
{
_double = true;
}
private bool _double = false;
public override string FormatErrorMessage(string name)
{
return String.Format(ErrorMessageString, name, this.Minimum, this.Maximum);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(this.ErrorMessage),
ValidationType = "range",
};
rule.ValidationParameters.Add("min", this.Minimum);
rule.ValidationParameters.Add("max", this.Maximum);
yield return rule;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
return null;
if (String.IsNullOrEmpty(value.ToString()))
return null;
if (_double)
{
var val = Convert.ToDouble(value);
if (val >= Convert.ToDouble(this.Minimum) && val <= Convert.ToDouble(this.Maximum))
return null;
}
else
{
var val = Convert.ToInt32(value);
if (val >= Convert.ToInt32(this.Minimum) && val <= Convert.ToInt32(this.Maximum))
return null;
}
return new ValidationResult(
FormatErrorMessage(this.ErrorMessage)
);
}
}
Example usage:
[ConfigRange(1)]
public int MyInt { get; set; }
[ConfigRange(1.1, ErrorMessage = "This one has gotta be between {1} and {2}!")]
public double MyDouble { get; set; }
The first example will return the default error message, and the second will return your custom error message. Both will use the range values defined in web.config.
You won't be able to do that in the attribute declaration on the property as the values need to be known at compile time. The easiest way that I could see of doing this would be to derive an attribute class from RangeAttribute and set the property values to come from web.config in the derived class. Something like
public class RangeFromConfigurationAttribute : RangeAttribute
{
public RangeFromConfigurationAttribute()
: base(int.Parse(WebConfigurationManager.AppSettings["MinBalance"]), int.Parse(WebConfigurationManager.AppSettings["MaxDeposit"]))
{
}
}
May want to come up with a better name though :)
Thinking out loud here, but ConfigRange attribute dictates that the config must be present for this to work. Can you not write a static class that would read your values from web.config, app.config or whatever you see fit, and then use that static class in existing range attribute?
public static class RangeReader
{
public static double Range1
{
// Replace this with logic to read from config file
get { return 20.0d; }
}
}
Then annotate your property with:
[Range(ConfigReader.Range1, 25.0d)]
I know that static classes are bad and there might well be a good reason for not doing this,but I thought i'll give a go.

attribute dependent on another field

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};
[RequiredIf("retired",true)]
public string retirementAge {get, set};
How can I do that?
Thank you.
Take a look at this: http://blogs.msdn.com/b/simonince/archive/2010/06/04/conditional-validation-in-mvc.aspx
I've modded the code somewhat to suit my needs. Perhaps you benefit from those changes as well.
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentUpon { get; set; }
public object Value { get; set; }
public RequiredIfAttribute(string dependentUpon, object value)
{
this.DependentUpon = dependentUpon;
this.Value = value;
}
public RequiredIfAttribute(string dependentUpon)
{
this.DependentUpon = dependentUpon;
this.Value = null;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
{
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{ }
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
// no client validation - I might well blog about this soon!
return base.GetClientValidationRules();
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentUpon);
if (field != null)
{
// get the value of the dependent property
var value = field.GetValue(container, null);
// compare the value against the target value
if ((value != null && Attribute.Value == null) || (value != null && value.Equals(Attribute.Value)))
{
// match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
// validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
}
Then use it:
public DateTime? DeptDateTime { get; set; }
[RequiredIf("DeptDateTime")]
public string DeptAirline { get; set; }
Just use the Foolproof validation library that is available on Codeplex:
https://foolproof.codeplex.com/
It supports, amongst others, the following "requiredif" validation attributes / decorations:
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
To get started is easy:
Download the package from the provided link
Add a reference to the included .dll file
Import the included javascript files
Ensure that your views references the included javascript files from within its HTML for unobtrusive javascript and jquery validation.
Using NuGet Package Manager I intstalled this: https://github.com/jwaliszko/ExpressiveAnnotations
And this is my Model:
using ExpressiveAnnotations.Attributes;
public bool HasReferenceToNotIncludedFile { get; set; }
[RequiredIf("HasReferenceToNotIncludedFile == true", ErrorMessage = "RelevantAuditOpinionNumbers are required.")]
public string RelevantAuditOpinionNumbers { get; set; }
I guarantee you this will work!
I have not seen anything out of the box that would allow you to do this.
I've created a class for you to use, it's a bit rough and definitely not flexible.. but I think it may solve your current problem. Or at least put you on the right track.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
namespace System.ComponentModel.DataAnnotations
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' is required";
private readonly object _typeId = new object();
private string _requiredProperty;
private string _targetProperty;
private bool _targetPropertyCondition;
public RequiredIfAttribute(string requiredProperty, string targetProperty, bool targetPropertyCondition)
: base(_defaultErrorMessage)
{
this._requiredProperty = requiredProperty;
this._targetProperty = targetProperty;
this._targetPropertyCondition = targetPropertyCondition;
}
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, _requiredProperty, _targetProperty, _targetPropertyCondition);
}
public override bool IsValid(object value)
{
bool result = false;
bool propertyRequired = false; // Flag to check if the required property is required.
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
string requiredPropertyValue = (string) properties.Find(_requiredProperty, true).GetValue(value);
bool targetPropertyValue = (bool) properties.Find(_targetProperty, true).GetValue(value);
if (targetPropertyValue == _targetPropertyCondition)
{
propertyRequired = true;
}
if (propertyRequired)
{
//check the required property value is not null
if (requiredPropertyValue != null)
{
result = true;
}
}
else
{
//property is not required
result = true;
}
return result;
}
}
}
Above your Model class, you should just need to add:
[RequiredIf("retirementAge", "retired", true)]
public class MyModel
In your View
<%= Html.ValidationSummary() %>
Should show the error message whenever the retired property is true and the required property is empty.
Hope this helps.
Try my custom validation attribute:
[ConditionalRequired("retired==true")]
public string retirementAge {get, set};
It supports multiple conditions.

Resources