MVC: Custom attribute not populating in view - asp.net-mvc

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

Related

Client side validation for custom attribute

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);
});

Run time Conditional Validation (Required Field) in MVC 3.0

I have few Address line fields in my Model class, which changes its behavior (Required / not Required) based on the selected country / language.
For example UK should have 3 address line as Required input field, where as in case of US, it should be only 2 Required fields.
I have tried to use a custom validator "RequiredIf" & turning it on or off based on another nullable Boolean property; But its not working.
Concerned property of my Model is mentioed as floows:
public bool? IsAddressLine3Mandatory { get; set; }
[RequiredIf("IsAddressline3Mandatory", true, ErrorMessage = "Address line 3 is mandatory for UK")]
public string AddressLine3 { get; set; }
I am fetching the country information from our Database & setting the IsAddressline3Mandatory field in the HTTPGet Action method for that view.
I am trying to achieve both client side & server side validation to happen.
Code for RequiredIf goes as follows:
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
private RequiredAttribute _innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
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(this.DependentProperty);
if (field != null)
{
// get the value of the dependent property
var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);
// compare the value against the target value
if ((dependentvalue == null && this.TargetValue == null) ||
(dependentvalue != null && dependentvalue.Equals(this.TargetValue)))
{
// match => means we should try validating this field
if (!_innerAttribute.IsValid(value))
// validation failed - return an error
return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
string depProp = BuildDependentPropertyId(metadata, context as ViewContext);
// find the value on the control we depend on;
// if it's a bool, format it javascript style
// (the default is True or False!)
string targetValue = (this.TargetValue ?? "").ToString();
if (this.TargetValue.GetType() == typeof(bool))
targetValue = targetValue.ToLower();
rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("targetvalue", targetValue);
yield return rule;
}
private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
{
// build the ID of the property
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(this.DependentProperty);
// unfortunately this will have the name of the current field appended to the beginning,
// because the TemplateInfo's context has had this fieldname appended to it. Instead, we
// want to get the context as though it was one level higher (i.e. outside the current property,
// which is the containing object (our Person), and hence the same level as the dependent property.
var thisField = metadata.PropertyName + "_";
if (depProp.StartsWith(thisField))
// strip it off again
depProp = depProp.Substring(thisField.Length);
return depProp;
}
}

Change Validation For a Property in ASP.NET MVC 3 by Condition

This is my Model:
[RegularExpression(#"^08[589][0-9]{8}$", ErrorMessage = "Invalid Number!")]
public string Phone { get; set; }
[ForeignKey]
public long PhoneType { get; set; } // 1-CellPhone , 2-Phone
So I think to change RegularExpression Validation by Change PhoneType if I want say more specific:
if user select CellPhone from DropDownList the validation be
[RegularExpression(#"^08[589][0-9]{8}$", ErrorMessage = "Invalid Number!")]
and if select Phone the validation be
[RegularExpression("^[1-9][0-9]{9}$", ErrorMessage = "Invalid Number!")]
What is your suggestion?
You could write a custom validation attribute:
public class PhoneAttribute : ValidationAttribute
{
private readonly string _phoneTypeProperty;
public PhoneAttribute(string phoneTyperoperty)
{
_phoneTypeProperty = phoneTyperoperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_phoneTypeProperty);
if (property == null)
{
return new ValidationResult(string.Format("Unknown property: {0}", _phoneTypeProperty));
}
var phone = Convert.ToString(value, CultureInfo.CurrentCulture);
if (string.IsNullOrEmpty(phone))
{
return null;
}
var phoneType = (long)property.GetValue(validationContext.ObjectInstance, null);
Regex regex = null;
if (phoneType == 1)
{
regex = new Regex(#"^08[589][0-9]{8}$");
}
else if (phoneType == 2)
{
regex = new Regex("^[1-9][0-9]{9}$");
}
else
{
return new ValidationResult(string.Format("Unknown phone type: {0}", phoneType));
}
var match = regex.Match(phone);
if (match.Success && match.Index == 0 && match.Length == phone.Length)
{
return null;
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
and then decorate your view model property with this attribute:
public class MyViewModel
{
[Phone("PhoneType", ErrorMessage = "Invalid Number!")]
public string Phone { get; set; }
public long PhoneType { get; set; }
}
Another possibility (and which I would more than strongly recommend) if you want to make your life easier with validation is to use FluentValidation.NET. Just look at how easier it is to define validation rules instead of writing gazzilions of lines of plumbing code and no longer be able to understand which part is plumbing and which part is actual validation. With FluentValidation.NET there's no plumbing. You express your validation requirements in a fluent way:
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.Phone)
.Matches(#"^08[589][0-9]{8}$").When(x => x.PhoneType == 1)
.Matches("^[1-9][0-9]{9}$").When(x => x.PhoneType == 2);
}
}
Simply compare this validator with the previous one.

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