Client side validation for custom attribute - asp.net-mvc

Why is my client side validation not working.
Property is:
[Required(ErrorMessage = "Email address is required")]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
[NonCompanyEmailAttribute(ErrorMessage = "Email address of customer required (not company employees)")]
public string EmailAddress
The validation attribute is:
public class NonCompanyEmailAttribute : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
bool containscompany = !((string)value).ToLower().Contains("#company.com");
return containscompany;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "noncompanyemail"
};
}
}
In JS I have:
$.validator.addMethod("noncompanyemail", function (value, element, params) {
return value.toLowerCase().indexOf('company.com' > -1);
});
$.validator.unobtrusive.adapters.add("noncompanyemail", function (options) {
options.rules["noncompanyemail"] = true;
options.messages["noncompanyemail"] = options.message;
});
jquery.validate.js and jquery.validate.unobtrusive.js are included

I'm not sure what you mean by "not working" but your jquery validation function appears to be oddly formed. I assume you want it to return false (aka invalid) if the value contains company.com anywhere. If so, your method should be something like:
$.validator.addMethod("noncompanyemail", function (value, element, params) {
return (value.toLowerCase().indexOf('company.com') == -1);
});

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 to set min and max annotations on a property with Data Annotations Extensions?

How do i set the min and max annotation on a Property, with Data Annotations Extensions?
This is what i have tried:
[Required(ErrorMessage = "Dette felt er påkrævet!")]
[Digits(ErrorMessage = "Indtast kun cifre")]
[Min(8, ErrorMessage = "Brugernavnet skal være 8 tegn langt.")]
[Max(8, ErrorMessage = "Brugernavnet skal være 8 tegn langt.")]
[Display(Name = "Brugernavn")]
public string UserName { get; set; }
I get the following error:
Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: range
Min and Max are for decimal types. For strings you use the [StringLength] or the [RegularExpression] attributes:
[StringLength(8, MinimumLength = 8)]
public string UserName { get; set; }
I had the same problem as Kenci. I wanted to use the [Min] and [Max] at the same time so I could have a separate error message for each type of error, instead of a range. I too was confronted with...
Validation type names in unobtrusive client validation rules must be unique. The
following validation type was seen more than once: range
Because they decided to use the same validation name for both types!
I got around this by keeping the min, and creating my own Int Max.
Add this class somewhere, ideally in a CustomerValidation class
public class MaximumDecimalCheck : ValidationAttribute, IClientValidatable
{
private readonly int _max;
private readonly string _defaultErrorMessage = "";
public MaximumDecimalCheck(int max, string defaultErrorMessage)
: base(defaultErrorMessage)
{
_max = max;
_defaultErrorMessage = defaultErrorMessage.Replace("{0}", _max.ToString());
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
return null;
int intValue;
bool parsedDecimal = int.TryParse(value.ToString(), out intValue);
if (parsedDecimal)
{
if (intValue < _max)
{
return ValidationResult.Success;
}
}
return new ValidationResult(_defaultErrorMessage);
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, _max);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule();
modelClientValidationRule.ValidationType = "checkmaxint";
modelClientValidationRule.ErrorMessage = _defaultErrorMessage;
modelClientValidationRule.ValidationParameters.Add("maxint", _max.ToString());
return new List<ModelClientValidationRule> { modelClientValidationRule };
}
}
Then, in your model object,
[Required(ErrorMessage = "Monthly income (after tax) required")]
[DataType(DataType.Currency)]
[Display(Name = "Monthly income (after tax)")]
[Min(200, ErrorMessage = "You do not earn enough")]
[MaximumDecimalCheck(10000, "You earn to much a month")]
public decimal? MonthlyIncomeAfterTax { get; set; }
the property type can be decimal, int etc... however it will only parse it as an int.
I failed to create a static type of a decimal so quickly gave in, if someone can answer that question i'd be very happy.
After adding the notation, reference the following javascript.
(function ($) {
/*
START CHECK MAX INT - UNOBTRUSIVE JAVASCRIPT
START CHECK MAX INT - UNOBTRUSIVE JAVASCRIPT
START CHECK MAX INT - UNOBTRUSIVE JAVASCRIPT
*/
jQuery.validator.unobtrusive.adapters.add("checkmaxint", ['maxint'], function (options) {
options.rules["checkmaxint"] = options.params;
options.messages["checkmaxint"] = options.message;
});
jQuery.validator.addMethod("checkmaxint", function (value, element, params) {
var maxValue = params.maxint;
var inputValue = value;
var parsedInt = parseInt(inputValue);
if (isNaN(parsedInt)) {
return false;
}
else if (parsedInt > maxValue) {
return false;
}
else {
return true;
}
});
/*
START CHECK MAX INT - UNOBTRUSIVE JAVASCRIPT
START CHECK MAX INT - UNOBTRUSIVE JAVASCRIPT
START CHECK MAX INT - UNOBTRUSIVE JAVASCRIPT
*/
} (jQuery));
I hope that helps.

Passing the ErrorMessage for clientside validation

Since there is no way to validate a property (with unobtrusive clientside validation) using multiple regex patterns (because validation type has to be unique) i decided to extend FluentValidation so i can do the following.
RuleFor(x => x.Name).NotEmpty().WithMessage("Name is required")
.Length(3, 20).WithMessage("Name must contain between 3 and 20 characters")
.Match(#"^[A-Z]").WithMessage("Name has to start with an uppercase letter")
.Match(#"^[a-zA-Z0-9_\-\.]*$").WithMessage("Name can only contain: a-z 0-9 _ - .")
.Match(#"[a-z0-9]$").WithMessage("Name has to end with a lowercase letter or digit")
.NotMatch(#"[_\-\.]{2,}").WithMessage("Name cannot contain consecutive non-alphanumeric characters");
The last thing i need to figure out is how to pass the errormessage which is set using WithMessage() via GetClientValidationRules() so it ends up in the "data-val-customregex[SOMEFANCYSTRINGHERETOMAKEITUNIQUE]" attribute on the input element.
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
var rule = new ModelClientValidationRule();
rule.ErrorMessage = [INSERT ERRORMESSAGE HERE];
rule.ValidationType = "customregex" + StringFunctions.RandomLetters(6);
rule.ValidationParameters.Add("pattern", pattern);
yield return rule;
}
I've been looking at the FluentValidation sourcecode, but couldn't figure it out. Anyone got any ideas?
I've been discussing how to do this with Jeremy Skinner (the creator of Fluent Validation) at
http://fluentvalidation.codeplex.com/discussions/253505
He was kind enough to write a complete example.
Update
Here is the code we came up with:
First the extensions, for both Match and NotMatch.
public static class Extensions
{
public static IRuleBuilderOptions<T, string> Match<T>(this IRuleBuilder<T, string> ruleBuilder, string expression)
{
return ruleBuilder.SetValidator(new MatchValidator(expression));
}
public static IRuleBuilderOptions<T, string> NotMatch<T>(this IRuleBuilder<T, string> ruleBuilder, string expression) {
return ruleBuilder.SetValidator(new MatchValidator(expression, false));
}
}
The used interface for the validator
public interface IMatchValidator : IPropertyValidator
{
string Expression { get; }
bool MustMatch { get; }
}
The actual validator:
public class MatchValidator : PropertyValidator, IMatchValidator
{
string expression;
bool mustMatch;
public MatchValidator(string expression, bool mustMatch = true)
: base(string.Format("The value {0} match with the given expression, while it {1}.", mustMatch ? "did not" : "did", mustMatch ? "should" : "should not"))
{
this.expression = expression;
this.mustMatch = mustMatch;
}
protected override bool IsValid(PropertyValidatorContext context)
{
return context.PropertyValue == null ||
context.PropertyValue.ToString() == string.Empty ||
Regex.IsMatch(context.PropertyValue.ToString(), expression) == mustMatch;
}
public string Expression
{
get { return expression; }
}
public bool MustMatch {
get { return mustMatch; }
}
}
The adaptor to register the validator:
public class MatchValidatorAdaptor : FluentValidationPropertyValidator
{
public MatchValidatorAdaptor(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator)
: base(metadata, controllerContext, rule, validator)
{
}
IMatchValidator MatchValidator
{
get { return (IMatchValidator)Validator; }
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var formatter = new MessageFormatter().AppendPropertyName(Rule.PropertyDescription);
string errorMessage = formatter.BuildMessage(Validator.ErrorMessageSource.GetString());
yield return new ModelClientValidationMatchRule(MatchValidator.Expression, MatchValidator.MustMatch, errorMessage);
}
}
And finally where the magic happens:
public class ModelClientValidationMatchRule : ModelClientValidationRule
{
public ModelClientValidationMatchRule(string expression, bool mustMatch, string errorMessage)
{
if (mustMatch)
base.ValidationType = "match";
else
base.ValidationType = "notmatch";
base.ValidationType += StringFunctions.RandomLetters(6);
base.ErrorMessage = errorMessage;
base.ValidationParameters.Add("expression", expression);
}
}
Update 2:
Javascript to wireup jQuery.validator:
(function ($) {
function attachMatchValidator(name, mustMatch) {
$.validator.addMethod(name, function (val, element, expression) {
var rg = new RegExp(expression, "gi");
return (rg.test(val) == mustMatch);
});
$.validator.unobtrusive.adapters.addSingleVal(name, "expression");
}
$("input[type=text]").each(function () {
$.each(this.attributes, function (i, attribute) {
if (attribute.name.length == 20 && attribute.name.substring(0, 14) == "data-val-match")
attachMatchValidator(attribute.name.substring(9, 20), true);
if (attribute.name.length == 23 && attribute.name.substring(0, 17) == "data-val-notmatch")
attachMatchValidator(attribute.name.substring(9, 23), false);
});
});
} (jQuery));
A little off topic, but maybe helpful. Regex are pretty powerful, have you considered combining all the rules in one regex? I think that's why the attributes providing regex validation usually don't allow multiple instances per property.
So for your example, your regex would be:
"^[A-Z]([a-zA-Z0-9][_\-\.]{0,1}[a-zA-Z0-9]*)*[a-z0-9]$"
And a handy place to test it: http://derekslager.com/blog/posts/2007/09/a-better-dotnet-regular-expression-tester.ashx

Int or Number DataType for DataAnnotation validation attribute

On my MVC3 project, I store score prediction for football/soccer/hockey/... sport game. So one of properties of my prediction class looks like this:
[Range(0, 15, ErrorMessage = "Can only be between 0 .. 15")]
[StringLength(2, ErrorMessage = "Max 2 digits")]
[Remote("PredictionOK", "Predict", ErrorMessage = "Prediction can only be a number in range 0 .. 15")]
public int? HomeTeamPrediction { get; set; }
Now, I need also change error message for a data type, int in my case. There is some default one used - "The field HomeTeamPrediction must be a number.". Need to find a way how to change this error message. This validation message also seem to take prediction for Remote validation one.
I've tried [DataType] attribute but this does not seem to be plain number in system.componentmodel.dataannotations.datatype enumeration.
For any number validation you have to use different different range validation as per your requirements :
For Integer
[Range(0, int.MaxValue, ErrorMessage = "Please enter valid integer Number")]
for float
[Range(0, float.MaxValue, ErrorMessage = "Please enter valid float Number")]
for double
[Range(0, double.MaxValue, ErrorMessage = "Please enter valid doubleNumber")]
Try one of these regular expressions:
// for numbers that need to start with a zero
[RegularExpression("([0-9]+)")]
// for numbers that begin from 1
[RegularExpression("([1-9][0-9]*)")]
hope it helps :D
Use regex in data annotation
[RegularExpression("([0-9]+)", ErrorMessage = "Please enter valid Number")]
public int MaxJsonLength { get; set; }
public class IsNumericAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
decimal val;
var isNumeric = decimal.TryParse(value.ToString(), out val);
if (!isNumeric)
{
return new ValidationResult("Must be numeric");
}
}
return ValidationResult.Success;
}
}
Try this attribute :
public class NumericAttribute : ValidationAttribute, IClientValidatable {
public override bool IsValid(object value) {
return value.ToString().All(c => (c >= '0' && c <= '9') || c == '-' || c == ' ');
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.DisplayName),
ValidationType = "numeric"
};
yield return rule;
}
}
And also you must register the attribute in the validator plugin:
if($.validator){
$.validator.unobtrusive.adapters.add(
'numeric', [], function (options) {
options.rules['numeric'] = options.params;
options.messages['numeric'] = options.message;
}
);
}
I was able to bypass all the framework messages by making the property a string in my view model.
[Range(0, 15, ErrorMessage = "Can only be between 0 .. 15")]
[StringLength(2, ErrorMessage = "Max 2 digits")]
[Remote("PredictionOK", "Predict", ErrorMessage = "Prediction can only be a number in range 0 .. 15")]
public string HomeTeamPrediction { get; set; }
Then I need to do some conversion in my get method:
viewModel.HomeTeamPrediction = databaseModel.HomeTeamPrediction.ToString();
and post method:
databaseModel.HomeTeamPrediction = int.Parse(viewModel.HomeTeamPrediction);
This works best when using the range attribute, otherwise some additional validation would be needed to make sure the value is a number.
You can also specify the type of number by changing the numbers in the range to the correct type:
[Range(0, 10000000F, ErrorMessageResourceType = typeof(GauErrorMessages), ErrorMessageResourceName = nameof(GauErrorMessages.MoneyRange))]
You can write a custom validation attribute:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public class Numeric : ValidationAttribute
{
public Numeric(string errorMessage) : base(errorMessage)
{
}
/// <summary>
/// Check if given value is numeric
/// </summary>
/// <param name="value">The input value</param>
/// <returns>True if value is numeric</returns>
public override bool IsValid(object value)
{
return decimal.TryParse(value?.ToString(), out _);
}
}
On your property you can then use the following annotation:
[Numeric("Please fill in a valid number.")]
public int NumberOfBooks { get; set; }
almost a decade passed but the issue still valid with Asp.Net Core 2.2 as well.
I managed it by adding data-val-number to the input field the use localization on the message:
<input asp-for="Age" data-val-number="#_localize["Please enter a valid number."]"/>
ASP.NET Core 3.1
This is my implementation of the feature, it works on server side as well as with jquery validation unobtrusive with a custom error message just like any other attribute:
The attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MustBeIntegerAttribute : ValidationAttribute, IClientModelValidator
{
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
var errorMsg = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-mustbeinteger", errorMsg);
}
public override bool IsValid(object value)
{
return int.TryParse(value?.ToString() ?? "", out int newVal);
}
private bool MergeAttribute(
IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
Client side logic:
$.validator.addMethod("mustbeinteger",
function (value, element, parameters) {
return !isNaN(parseInt(value)) && isFinite(value);
});
$.validator.unobtrusive.adapters.add("mustbeinteger", [], function (options) {
options.rules.mustbeinteger = {};
options.messages["mustbeinteger"] = options.message;
});
And finally the Usage:
[MustBeInteger(ErrorMessage = "You must provide a valid number")]
public int SomeNumber { get; set; }

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