I have a custom ValidationAttribute, however I only want to validate this property if a CheckBox is checked.
I've made my class inherit from IValidationObject and am using the Validate method to perform any custom validation, however can I use a custom ValidationAttribute here instead of duplicating the code? And if so, how?
public class MyClass : IValidatableObject
{
public bool IsReminderChecked { get; set; }
public bool EmailAddress { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsReminderChecked)
{
// How can I validate the EmailAddress field using
// the Custom Validation Attribute found below?
}
}
}
// Custom Validation Attribute - used in more than one place
public class EmailValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var email = value as string;
if (string.IsNullOrEmpty(email))
return false;
try
{
var testEmail = new MailAddress(email).Address;
}
catch (FormatException)
{
return false;
}
return true;
}
}
It's possible to validate a property based on the value of another property, but there are a few hoops to jump through to make sure the validation engine works the way you expect. Simon Ince's RequiredIfAttribute has a good approach and it should be easy to modify it into a ValidateEmailIfAttribute just by adding your e-mail validation logic to the IsValid method.
For example, you could have your base validation attribute, just like you do now:
public class ValidateEmailAttribute : ValidationAttribute
{
...
}
and then define the conditional version, using Ince's approach:
public class ValidateEmailIfAttribute : ValidationAttribute, IClientValidatable
{
private ValidateEmailAttribute _innerAttribute = new ValidateEmailAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public ValidateEmailIfAttribute(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;
}
// Client-side validation code omitted for brevity
}
Then you could just have something like:
[ValidateEmailIf("IsReminderChecked", true)]
public bool EmailAddress { get; set; }
Related
I have this Dto class for the web api controller in .NET Core 2.2 MVC.
ApplicationDocumentType is an enum
public class DocumentUploadDto
{
[FileValidation]
public IFormFile File { get; set; }
public ApplicationDocumentType DocumentType { get; set; }
public Guid Id { get; set; }
}
and
public enum ApplicationDocumentType
{
BANKSTATEMENT,
NRIC
}
and the below class implements the [FileValidation]
public class FileValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var file = value as IFormFile;
// some code removed for brevity
if (!AllowMimeTypes.Contains(file.ContentType))
{
ErrorMessage = "Invalid file type.";
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Now I need to validate based on DocumentType. How do I pass DocumentType into FileValidationAttribute to do some validation?
Currently all DocumentType is having the same validation. But now I need to customize the validation based on DocumentType.
Thanks Richard for the clue, but I just keep getting the first enum value.
public class FileValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var containerType = validationContext.ObjectType;
var documentType = containerType.GetProperty("DocumentType");
var file = value as IFormFile;
if (file == null)
return new ValidationResult("No file found.");
if (documentType != null)
{
var documentTypeValue = documentType.GetValue(validationContext.ObjectInstance, null);
if (documentTypeValue.ToString() == "NRIC"
&& file.ContentType == "application/pdf")
{
ErrorMessage = "Invalid file type. Pdf file type is not allowed for NRIC.";
return new ValidationResult(ErrorMessage);
}
}
// some code removed for brevity purpose.
}
}
If document type is static, you can simply add an attribute to your Attribute.
public class FileValidationAttribute : ValidationAttribute
{
public FileValidationAttribute(params string[] allowMimeTypes)
{
AllowMimeTypes = allowMimeTypes;
}
public string[] AllowMimeTypes { get; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var file = value as IFormFile;
// some code removed for brevity
if (!AllowMimeTypes.Contains(file.ContentType))
{
ErrorMessage = "Invalid file type.";
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
and then, in Dto, add allowed Mime Types
public class DocumentUploadDto
{
[FileValidation("text/javascript", "text/html")]
public IFormFile File { get; set; }
public ApplicationDocumentType DocumentType { get; set; }
public Guid Id { get; set; }
}
If you need to pass mime type dynamically, then I suggest looking at https://fluentvalidation.net/ which allows you to easily add data to validation context and write more fluent validators.
I am trying to write my own ValidationAttribute for which I want to pass the value of a parameter of my class to the ValidationAttribute. Very simple, if the boolean property is true, the property with the ValidationAttribute on top should not be null or empty.
My class:
public class Test
{
public bool Damage { get; set; }
[CheckForNullOrEmpty(Damage)]
public string DamageText { get; set; }
...
}
My Attribute:
public class CheckForNullOrEmpty: ValidationAttribute
{
private readonly bool _damage;
public RequiredForWanrnleuchte(bool damage)
{
_damage = damage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string damageText = validationContext.ObjectType.GetProperty(validationContext.MemberName).GetValue(validationContext.ObjectInstance).ToString();
if (_damage == true && string.IsNullOrEmpty(damageText))
return new ValidationResult(ErrorMessage);
return ValidationResult.Success;
}
}
However, I cannot simply pass the property inside the class to the ValidationAttribute like that. What would be a solution to pass the value of that property?
Instead of passing the bool value to the CheckForNullOrEmptyAttribute, you should pass the name of the corresponding property; within the attribute, you then can retrieve this bool value from the object instance being validated.
The CheckForNullOrEmptyAttribute below, can be applied on your model as shown here.
public class Test
{
public bool Damage { get; set; }
[CheckForNullOrEmpty(nameof(Damage))] // Pass the name of the property.
public string DamageText { get; set; }
}
public class CheckForNullOrEmptyAttribute : ValidationAttribute
{
public CheckForNullOrEmptyAttribute(string propertyName)
{
PropertyName = propertyName;
}
public string PropertyName { get; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var hasValue = !string.IsNullOrEmpty(value as string);
if (hasValue)
{
return ValidationResult.Success;
}
// Retrieve the boolean value.
var isRequired =
Convert.ToBoolean(
validationContext.ObjectInstance
.GetType()
.GetProperty(PropertyName)
.GetValue(validationContext.ObjectInstance)
);
if (isRequired)
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Within a Class I have a Static List of values which are allowed
private static List<string> allowedClassNames = new List<string> {"Real Estate", "Factored Debt"};
And I also have an attribute of that class, which I want to restrict to being values in that list.
[Required]
public string assetClassName { get; set; }
I want to do this at the model level, so it works in either a REST or view context.
How would I implement forcing the value in the submission to be limited to that list?
Thanks!
Here's Where I wound up - Not fully tested yet, but to give an idea to future posters.
class MustContainAttribute : RequiredAttribute
{
public string Field { get; private set; }
List<string> allowed;
public MustContainAttribute(string validateField)
{
this.Field = validateField;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
switch (Field)
{
case "assetClassName":
allowed = new List<string> { "Real Estate", "Factored Debt" };
break;
default:
return ValidationResult.Success;
}
if (!allowed.Contains(Field))
{
return new ValidationResult("Invalid Value");
}else{
return ValidationResult.Success;
}
}
}
Create a custom validation attribute:
public class ClassNameRequiredAttribute : RequiredAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
MyAssetClass myAssetClass = (MyAssetClass)type.GetProperty("MyAssetClass").GetValue(instance, null);
if (!string.IsNullOrEmpty(myAssetClass.assetClassName))
{
if (myAssetClass.allowedClassNames.Contains(myAssetClass.assetClassName))
{
return ValidationResult.Success;
}
}
return new ValidationResult(ErrorMessage);
}
}
And in your model:
[ClassNameRequired(ErrorMessage="Your error message.")]
public string assetClassName { get; set; }
As mentioned in the comments you can create your own ValidationAttribute. This is useful if you have this validation on multiple models or if you want to implement client side validation as well (JavaScript)
However, A quick and easy way to do one off validations like this is the IValidatableObject. You can use it as follows:
public class AssetModel:IValidatableObject
{
private static List<string> allowedClassNames = new List<string> {"Real Estate", "Factored Debt"};
[Required]
public string assetClassName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!allowedClassNames.Contains(assetClassName)
{
yield new ValidationResult("Not an allowed value", new string[] { "assetClassName" } );
}
}
}
I have this custom attribute that was working some time ago:
// This checks to see if the question is mandatory and needs a answer
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CheckMandatoryAttribute : ValidationAttribute, IClientValidatable
{
public bool IsMandatory { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
bool isMandatory = (bool)validationContext.ObjectType.GetProperty("IsMandatory").GetValue(validationContext.ObjectInstance, null);
this.IsMandatory = isMandatory;
if (isMandatory && value == null)
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success; ;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessage,
ValidationType = "checkmandatory"
};
rule.ValidationParameters.Add("ismandatory", IsMandatory);
yield return rule;
}
}
I'm 100% sure that it used to work.
The problem is now that when GetClientValidationRules() is called, the IsMandatory boolean is always false, even though it gets set as true when the IsValid() method is called before hand.
Here is how the custom attribute is being used:
public class ConductQuestionAnswerViewModel : BaseModel
{
public bool IsMandatory { get; set; }
[CheckMandatory(ErrorMessage = "Your response is mandatory.")]
[CheckEval(ErrorMessage = "You need to make an evaluation for your response.")]
[UIHint("Radio")]
public int? YourResponse { get; set; }
}
The IsValid() method is returning the correct value, so server-side validation is working.
However, as the GetClientValidationRules() is not working, the incorrect value is being set on the property data attribute. Therefore, the client-side validation is not working.
I have next (simplified) view model:
public class RegisterModel
{
public string UserName { get; set; }
[MustExistIf("SomeProperty", "some value", "SomeOtherProperty", ErrorMessage = "You have to select something")]
public string LastName { get; set; }
public AddressModel Address { get; set; }
}
public class AddressModel
{
public string Street { get; set; }
public string House { get; set; }
}
and I have custom validator
public class MustExistIfAttribute : ValidationAttribute, IClientValidatable
{
private string _masterName { get; set; }
private object _masterValue { get; set; }
private string _dependantName { get; set; }
public MustExistIfAttribute(string masterName, object masterValue, string dependantName)
{
this._masterName = masterName;
this._masterValue = masterValue;
this._dependantName = dependantName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get value of master property
var masValue = _getValue(validationContext.ObjectInstance, _masterName);
// get value of property whch depends on master property
var depValue = _getValue(validationContext.ObjectInstance, _dependantName);
if (masValue.Equals(_masterValue)) // if value in request is equal to value in specified in data annotation
{
if (depValue == null) // if dependant value does not exist
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public override bool IsValid(object value)
{
return base.IsValid(value);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "mustexistif",
ErrorMessage = FormatErrorMessage(metadata.DisplayName)
};
modelClientValidationRule.ValidationParameters.Add("mastername", this._masterName);
modelClientValidationRule.ValidationParameters.Add("mastervalue", this._masterValue);
modelClientValidationRule.ValidationParameters.Add("dependantname", this._dependantName);
yield return modelClientValidationRule;
}
private static object _getValue(object objectInstance, string propertyName)
{
...
}
}
I have next javascript (please neglect returning false in mustexitif method - it's just for test purposes)
(function () {
jQuery.validator.addMethod('mustexistif', function (value, element, params) {
var masterName = params['mastername'];
var masterValue = params['mastervalue'];
var dependantName = params['dependantname'];
return false;
});
var setValidationValues = function (options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
};
var $Unob = $.validator.unobtrusive;
$Unob.adapters.add("mustexistif", ["mastername", "mastervalue", "dependantname"], function (options) {
var value = {
mastername: options.params.mastername,
mastervalue: options.params.mastervalue,
dependantname: options.params.dependantname
};
setValidationValues(options, "mustexistif", value);
});
})();
It works as expected when I decorate LastName property of RegisterModel class with MustExistIf annotation (like in provided code).
But what I really want is to decorate complex Address property of RegisterModel with MustExistIf annotation. Problem is that when I do that no unobrusive adapter gets registered (javascript doing that IS NOT triggered).
So, there is difference when I decoreate simple and complex properties. My solution does not allow me to decorate properties of Address class (FYI, I tried that and then also validation is working fine). Is there a way to accomplish what I intended? Am I missing something? Woud solution be to validate on model level? But then is it possible to do client side validation?
Maybe you can use Remote Validation.
http://msdn.microsoft.com/en-us/library/gg508808%28v=vs.98%29.aspx