I have property in my PersonDTO class:
[EmailAddress]
public string Email { get; set; }
It works fine, except I want to allow empty strings as values for my model, if I send JSON from client side:
{ Email: "" }
I got 400 bad request response and
{"$id":"1","Message":"The Email field is not a valid e-mail address."}
However, it allows omitting email value:
{ FirstName: "First", LastName: 'Last' }
I also tried:
[DataType(DataType.EmailAddress, ErrorMessage = "Email address is not valid")]
but it does not work.
As far as I understood, Data Annotations Extensions pack does not allow empty string either.
Thus, I wonder if there is a way to customize the standard EmailAddressAttribute to allow empty strings so I do not have to write custom validation attribute.
You have two options:
Convert string.Empty to null on the Email field. Many times that is perfectly acceptable. You can make this work globally, or by simply making your setter convert string.Empty to null on the email field.
Write a custom EmailAddress attribute, since EmailAddressAttribute is sealed you can wrap it and write your own forwarding IsValid method.
Sample:
bool IsValid(object value)
{
if (value == string.Empty)
{
return true;
}
else
{
return _wrappedAttribute.IsValid(value);
}
}
Expansion on option 1 (from Web Api not converting json empty strings values to null)
Add this converter:
public class EmptyToNullConverter : JsonConverter
{
private JsonSerializer _stringSerializer = new JsonSerializer();
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
string value = _stringSerializer.Deserialize<string>(reader);
if (string.IsNullOrEmpty(value))
{
value = null;
}
return value;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
_stringSerializer.Serialize(writer, value);
}
}
and use on the property:
[JsonConverter(typeof(EmptyToNullConverter))]
public string EmailAddress {get; set; }
or globally in WebApiConfig.cs:
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(
new EmptyToNullConverter());
It's Easy. Do This. Bye
private string _Email;
[EmailAddress(ErrorMessage = "Ingrese un formato de email vĂ¡lido")]
public string Email { get { return _Email; } set { _Email = string.IsNullOrWhiteSpace(value) ? null : value; } }
I used the suggestion from Yishai Galatzer to make a new ValidationAttribute called EmailAddressThatAllowsBlanks:
namespace System.ComponentModel.DataAnnotations
{
public class EmailAddressThatAllowsBlanks : ValidationAttribute
{
public const string DefaultErrorMessage = "{0} must be a valid email address";
private EmailAddressAttribute _validator = new EmailAddressAttribute();
public EmailAddressThatAllowsBlanks() : base(DefaultErrorMessage)
{
}
public override bool IsValid(object value)
{
if (string.IsNullOrEmpty(value.ToString()))
return true;
return _validator.IsValid(value.ToString());
}
}
}
Set TargetNullValue property of the binding to an empty string.
TargetNullValue=''
Related
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
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;
}
}
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; }
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....
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.