This tutorial requires some previous knowledge of the Data Annotations Namespace , along with .NET applications.
Data Annotations , an overview
Data Annotations allow you to configure class models and add various attributes that specify how the property it is applied to should be treated e.g.
- Required
- MaxLength
- MinLength
- Display (Name ,Order, Description etc)
- Range
- RegularExpression
- Compare
These are just a few of the options available.
You can read more on Data Annotations Here
Why Write Your Own ?
Well its simple really - while the list of options is expansive we are often required to implement restrictions that don't exist.
Consider a common form to change passwords. It would require your existing password and the new password. Often you see a requirement that the new password is not the same as the current password. Or maybe just that the password is not the same as the Username.
Data Annotations has an option to make sure that two properties are the same using Compare
- you could verify a user enters a password and reenters to confirm. But it does not have the opposite to this to prevent two properties being the same.
We will use a CustomValidator
to build this option.
Get Started
Create a new class file. Within this import the below namespaces to get started:
using System.Reflection;
using System.ComponentModel.DataAnnotations;
Next create a new class named NotEqualTo and inherit from ValidationAttribute
and override the IsValid
method with our own. We'll also need to create the constructor that will accept the name of the form property we need to compare to.
private readonly string _other;
public NotEqualto (string other)
{
// This is the name of the property to compare to
_other = other;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
....
}
Now we need to implement the logic in the IsValid method. The method takes two parameters
- object value - This is the value entered by the user for that field.
- ValidationContext validationContext - This is the instance object for our validation, and we can use this to access the other properties.
The key to all this is the validationContext. We need to use it in conjunction with the string we pass to the constructor.
// Get the Other Property
var otherProperty = validationContext.ObjectType.GetProperty(_other);
//Now use it to get the Value
var otherValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
Now this is all well and good but we should add in some checks in case the property doesn't exist and return an error message using the ValidationResult
like so :
// Get the Other Property
var otherProperty = validationContext.ObjectType.GetProperty(_other);
//Check if it exists
if (otherProperty == null)
{
return new ValidationResult( string.Format("Property {0} not found", _other) );
}
//Now use it to get the Value
var otherValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
Then all we need to do is compare the values of the two properties, and if they are equal then set the ValidationResult
to return an error.
if (object.Equals(value, otherValue))
{
// We can access the Display name like so
var otherDisplayAttribute = otherProperty.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
string otherName = "";
if (otherDisplayAttribute != null)
{
otherName = otherDisplayAttribute.Name;
}
else
{
otherName = otherProperty.Name;
}
// Set the Error Message if the two values are the same.
this.ErrorMessage = $"{validationContext.DisplayName} cannot be the same as {otherName}";
return new ValidationResult(this.ErrorMessage);
}
Simple enough.
Apply the Validator
You can apply the Custom validator like you would any other Data Annotation ! e.g. [NotEqualto("name_of_other_property")]
. The name of the property to compare to must be passed in as well.
A more detailed example :
public class PasswordForm
{
[Required]
[Display(Name = "Current Password")]
public string Password{get;set;}
[Required]
[NotEqualto("Password")]
[Display(Name = "New Password")]
public string NewPassword{get;set;}
}
The Final Code
By now you should have something similar to the below:
using System.Reflection;
using System.ComponentModel.DataAnnotations;
namespace SampleProject.NotEqualsValidation
{
public class NotEqualto : ValidationAttribute
{
private readonly string _other;
public NotEqualto (string other)
{
// This is the name of the property to compare to
_other = other;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherProperty = validationContext.ObjectType.GetProperty(_other);
if (otherProperty == null)
{
return new ValidationResult( string.Format("Property {0} not found", _other) );
}
var otherValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (object.Equals(value, otherValue))
{
var otherDisplayAttribute = otherProperty.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
string otherName = "";
if (otherDisplayAttribute != null)
{
otherName = otherDisplayAttribute.Name;
}
else
{
otherName = otherProperty.Name;
}
this.ErrorMessage = $"{validationContext.DisplayName} cannot be the same as {otherName}";
return new ValidationResult(this.ErrorMessage);
}
return null;
}
}
}
Finito
I hope you've found this useful and that it gets you out of any binds you may find yourself in !
See a full example Git project below
Top comments (0)