Custom Validation Attribute That Compares the Value of My Property with Another Property's Value in My Model Class

Custom Validation Attributes: Comparing two properties in the same model

You can create a custom validation attribute for comparison two properties. It's a server side validation:

public class MyViewModel
{
[DateLessThan("End", ErrorMessage = "Not valid")]
public DateTime Begin { get; set; }

public DateTime End { get; set; }
}

public class DateLessThanAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;

public DateLessThanAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
var currentValue = (DateTime)value;

var property = validationContext.ObjectType.GetProperty(_comparisonProperty);

if (property == null)
throw new ArgumentException("Property with this name not found");

var comparisonValue = (DateTime)property.GetValue(validationContext.ObjectInstance);

if (currentValue > comparisonValue)
return new ValidationResult(ErrorMessage);

return ValidationResult.Success;
}
}

Update:
If you need a client side validation for this attribute, you need implement an IClientModelValidator interface:

public class DateLessThanAttribute : ValidationAttribute, IClientModelValidator
{
...
public void AddValidation(ClientModelValidationContext context)
{
var error = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
context.Attributes.Add("data-val", "true");
context.Attributes.Add("data-val-error", error);
}
}

The AddValidation method will add attributes to your inputs from context.Attributes.

Sample Image

You can read more here IClientModelValidator

Custom validation attribute that compares the value of my property with another property's value in my model class

Here's how you could obtain the other property value:

public class CustomAttribute : ValidationAttribute
{
private readonly string _other;
public CustomAttribute(string other)
{
_other = other;
}

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_other);
if (property == null)
{
return new ValidationResult(
string.Format("Unknown property: {0}", _other)
);
}
var otherValue = property.GetValue(validationContext.ObjectInstance, null);

// at this stage you have "value" and "otherValue" pointing
// to the value of the property on which this attribute
// is applied and the value of the other property respectively
// => you could do some checks
if (!object.Equals(value, otherValue))
{
// here we are verifying whether the 2 values are equal
// but you could do any custom validation you like
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}

C# Custom Validation Attributes: Comparing two Dates in the same model

Validation attributes are great for validating a single property. I wouldn't attempt to validate multiple properties with attributes, since you can't be certain in what order the fields will be assigned and when the validation should occur.

Instead I would implement IValidatableObject;

public class YourClass: IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (TerminationDate < OtherDate + TimeSpan.FromDays(3))
yield return new ValidationResult("... error message here ...");
}
}

How to validate field related to another fields

This should give you what you are looking for.

public class Report : ValidationAttribute
{
public int Score { get; set; }
public string Comment { get; set; }
public int[] ReasonIds { get; set; }

protected override ValidationResult IsValid(
object value, ValidationContext validationContext)
{
if(Score < 4 && (string.IsNullOrEmpty(Comment) || ReasonIds.Count() < 1))
{
return new ValidationResult(GeScoreErrorMessage());
}
return ValidationResult.Success;
}

private string GeScoreErrorMessage()
{
return $"If Score < 4 Comment and Reasons must be provided";
}
}

Use AdditionalFields to compare to field in a different class

You cannot use AdditionalFields to compare against values from another ViewModel. The reason is that the rules are added to jquery.validate.js by the jquery.validate.unobtrusive.js plugin (which reads the data-val-* attributes generated by the HtmlHelper methods). Specifically it is the adapters.add("remote", ["url", "type", "additionalfields"], function (options) { method that is pre-pending First to the property names.

One option would be to use a single 'flat' view model containing all properties.

If that is not desirable, then you can just write your own ajax code to call your server method that performs the validation. This actually has some added performance benefits as well. By default, after initial validation triggered by the .blur() event, validation is performed on every .keyup() event, meaning that you are potentially making a lot of ajax and database calls if the user initially entered an invalid value.

Remove the [Remote] attribute, and add the following script (I'll assume the properties are First.ABC and Second.XYZ)

$('#First_ABC').change(function() {
var url = '@Url.Action(...)'; // add your action name
var input = $(this);
var message = $('[data-valmsg-for="First.ABC"]'); // or give the element and id attribute
$.post(url, { abc: input.val(), xyz: $('#Second_XYZ').val() }, function(response) {
var isValid = response === true || response === "true";
if (isValid) {
input.addClass('valid').removeClass('input-validation-error');
message.empty().addClass('field-validation-valid').removeClass('field-validation-error');
} else {
input.addClass('input-validation-error').removeClass('valid');
message.text(response).addClass('field-validation-error').removeClass('field-validation-valid');
}
})
});

where the controller method would be

[HttpPost]
public ActionResult Validate(string abc, string xyz)
{
bool isValid = .... // code to validate
if (isValid)
{
return Json(true, JsonRequestBehaviour.AllowGet);
}
else
{
return Json("your error message", JsonRequestBehaviour.AllowGet)
}
}


Related Topics



Leave a reply



Submit