What is the opposite/negate of [Compare(" ")] data annotation" in ASP.NET?
i.e: two properties must hold different values.
public string UserName { get; set; }
[Something["UserName"]]
public string Password { get; set; }
You can use the [NotEqualTo] data annotation operator included in MVC Foolproof Validation. I used it right now and it works great!
MVC Foolproof is an open source library created by #nick-riggs and has a lot of available validators. Besides doing server side validation it also does client side unobtrusive validation.
Full list of built in validators you get out of the box:
Included Operator Validators
[Is]
[EqualTo]
[NotEqualTo]
[GreaterThan]
[LessThan]
[GreaterThanOrEqualTo]
[LessThanOrEqualTo]
Included Required Validators
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
This is the implementation (server side) of the link that #Sverker84 referred to.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UnlikeAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "The value of {0} cannot be the same as the value of the {1}.";
public string OtherProperty { get; private set; }
public UnlikeAttribute(string otherProperty)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType()
.GetProperty(OtherProperty);
var otherPropertyValue = otherProperty
.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
Usage:
public string UserName { get; set; }
[Unlike("UserName")]
public string AlternateId { get; set; }
Details about this implementation, and how to implement it client-side can be found here:
http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2
http://www.macaalay.com/2014/02/25/unobtrusive-client-and-server-side-not-equal-to-validation-in-mvc-using-custom-data-annotations/
The complete code for both server side and client side validation is as follows:
[AttributeUsage(AttributeTargets.Property)]
public class UnlikeAttribute : ValidationAttribute, IClientModelValidator
{
private string DependentProperty { get; }
public UnlikeAttribute(string dependentProperty)
{
if (string.IsNullOrEmpty(dependentProperty))
{
throw new ArgumentNullException(nameof(dependentProperty));
}
DependentProperty = dependentProperty;
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(DependentProperty);
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-unlike", ErrorMessage);
// Added the following code to account for the scenario where the object is deeper in the model's object hierarchy
var idAttribute = context.Attributes["id"];
var lastIndex = idAttribute.LastIndexOf('_');
var prefix = lastIndex > 0 ? idAttribute.Substring(0, lastIndex + 1) : string.Empty;
MergeAttribute(context.Attributes, "data-val-unlike-property", $"{prefix}{DependentProperty}");
}
private void MergeAttribute(IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return;
}
attributes.Add(key, value);
}
}
Then include the following in JavaScript:
$.validator.addMethod('unlike',
function (value, element, params) {
var propertyValue = $(params[0]).val();
var dependentPropertyValue = $(params[1]).val();
return propertyValue !== dependentPropertyValue;
});
$.validator.unobtrusive.adapters.add('unlike',
['property'],
function (options) {
var element = $(options.form).find('#' + options.params['property'])[0];
options.rules['unlike'] = [element, options.element];
options.messages['unlike'] = options.message;
});
Usage is as follows:
public int FromId { get; set; }
[Unlike(nameof(FromId), ErrorMessage = "From ID and To ID cannot be the same")]
public int ToId { get; set; }
Use this in your get/set logic:
stringA.Equals(stringB) == false
In addition to solution given by #Eitan K, If you want to use other property's display name instead of other property's name, use this snippet:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UnlikeAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "The value of {0} cannot be the same as the value of the {1}.";
public string OtherPropertyDisplayName { get; private set; }
public string OtherProperty { get; private set; }
public UnlikeAttribute(string otherProperty)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherPropertyDisplayName);
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType()
.GetProperty(OtherProperty);
var otherPropertyValue = otherProperty
.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
OtherPropertyDisplayName = otherProperty.GetCustomAttribute<DisplayAttribute>().Name;
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
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;
}
}
I'm attempting to create my own client side validation attribute that would validate the property of a form on submit. I have been referencing the following Microsoft document: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-2.1#custom-validation.
I am unsure on how to add the validation rule to jQuery's validator object. This is how far I have gotten:
My ValidationAttribute is as follows
public class CannotEqualValue : ValidationAttribute, IClientModelValidator
{
private readonly string _value;
public CannotEqualValue(string value)
{
_value = value;
}
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(
context.Attributes, "data-val-cannotbevalue", GetErrorMessage()); //???
MergeAttribute(
context.Attributes, "data-val-cannotbevalue-value", _value); //???
}
protected override ValidationResult IsValid(
object value,
ValidationContext validationContext)
{
var category = (Category) validationContext.ObjectInstance;
if (category.Name == _value)
return new ValidationResult(GetErrorMessage());
return ValidationResult.Success;
}
private bool MergeAttribute(
IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key)) return false;
attributes.Add(key, value);
return true;
}
private string GetErrorMessage()
{
return $"Name cannot be {_value}.";
}
}
The ValidationAttribute is used in a model like so
public class Category
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "Name is required and must not be empty.")]
[StringLength(200, ErrorMessage = "Name must not exceed 200 characters.")]
[CannotEqualValue("Red")]
public string Name { get; set; }
}
I am referencing both jQuery validation and unobtrusive in my page.
I am unsure on how to add the rule to jQuery's validator object:
$.validator.addMethod("cannotbevalue",
function(value, element, parameters) {
//???
});
$.validator.unobtrusive.adapters.add("cannotbevalue",
[],
function(options) {
//???
});
Your MergeAttribute(..) lines of code in the AddValidation() method are correct and will add the data-val-* attributes for client side validation.
Your scripts need to be
$.validator.addMethod("cannotbevalue", function(value, element, params) {
if ($(element).val() == params.targetvalue) {
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add('cannotbevalue', ['value'], function(options) {
options.rules['cannotbevalue'] = { targetvalue: options.params.value };
options.messages['cannotbevalue'] = options.message;
});
I have created a custom RequiredIf validator like this:
public class RequiredIfValidator : ValidationAttribute, IClientValidatable
{
RequiredAttribute _innerAttribute = new RequiredAttribute();
public string _dependentProperty { get; set; }
public object _targetValue { get; set; }
public RequiredIfValidator(string dependentProperty, object targetValue)
{
this._dependentProperty = dependentProperty;
this._targetValue = targetValue;
}
public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, _dependentProperty);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var field = validationContext.ObjectInstance.GetType().GetProperty(_dependentProperty);
if (field != null)
{
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
if ((dependentValue == null && _targetValue == null) ||(dependentValue.Equals(_targetValue)))
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "requiredif";
rule.ValidationParameters["dependentproperty"] = _dependentProperty;
rule.ValidationParameters["targetvalue"] = _targetValue;
yield return rule;
}
}
I have an enum with various test types like this:
public enum TestTypes
{
Hair = 1,
Urine = 2
}
My ViewModel has some properties like this:
public class TestViewModel
{
public TestTypes TestTypeId {get; set;}
[RequiredIfValidator("TestTypeId", TestTypes.Hair)]
public string HairSpecimenId {get; set;}
}
My custom RequiredIfValidator is not working in this scinario. Is it because of the enum data type? Any way to achieve this with enums
You logic in the IsValid() does not appear to be correct. It should be
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(_dependentProperty);
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue != null && otherPropertyValue.Equals(_targetValue ))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
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