DEV Community

Jamie Mc Manus
Jamie Mc Manus

Posted on

Creating a Server Side Data Annotations CustomValidator

This tutorial requires some previous knowledge of the Data Annotations Namespace , along with .NET applications.

Image description

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.

Image description

Get Started

Create a new class file. Within this import the below namespaces to get started:

using System.Reflection;
using System.ComponentModel.DataAnnotations;
Enter fullscreen mode Exit fullscreen mode

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)
        { 
    .... 
}
Enter fullscreen mode Exit fullscreen mode

Now we need to implement the logic in the IsValid method. The method takes two parameters

  1. object value - This is the value entered by the user for that field.
  2. 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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
            }
Enter fullscreen mode Exit fullscreen mode

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;}

}
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

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

GitHub logo JamieMcManus / CustomValidator

Example of a custom server side validator for data annotations

If you feel generous enough feel free to Buy Me A Coffee

Top comments (0)