How to Simplify Form Field Validations in Kentico

Learn how to save time and keep code clean with Data Annotations.

Data Annotations for validations are an easy way to create a validation that can be used for multiple fields or properties quickly. It can save time and keep code cleaner to do validations on data using these Data Annotations.

These Data Annotations are Decorations that look like tags for methods, fields, and properties. Some commonly used Decorations for fields and properties are [Required], [MaxLength], and [MinLength]. Decorations aren’t limited to just fields or properties, they can also be used on methods. This is commonly seen in controllers such as [HttpPost], [HttpGet], [Authorize], and [ValidateAntiforgeryToken]. Even the class for an API controller may be decorated with the [Route] attribute to specify how the controller and its methods should be found.

				
					[Route( "api/search" )]
				
			

Sometimes, we need to validate a field in a way that isn’t handled out of the box, or sometimes we just want a little more control over the messages from the validation. There are plenty of reasons to create a custom validation, and they aren’t all that scary. Let’s say for password validation, there are four different requirements:

  • Contains 1 upper case
  • Contains 1 number
  • Contains 1 symbol
  • Length of 8 or more

This can be handled with a single Regex, but the error message for each part of the validation will not let the user know exactly which requirement was not met. However, the Regex Data Annotation only allows one instance per field/property, so it’s not possible to add one for each individual error message.

Determining How the Validation Should Be Done

We have a couple of different solutions here; we could create a validation attribute for a custom regex validation that allows multiple attributes to be applied to a single field or create a custom attribute that contains the regex already but returns the error message for each regex condition if it does not exist. I prefer the latter, as modifying this later will be easier if the validation requirements change. Since the Regex validation is done in the data annotation logic, only the attribute would need to be modified to update the password validation for any password using this attribute. Creating an attribute that takes in a regex validation and allows multiple would be helpful in places where there may be a need to have different Regex validations, likely for something that is not a password.

Creating the Validation Logic

Before I create the attribute, I want to separate the validation itself. This way, if there is any reason to do a password validation outside of the attribute itself, it can be called on any string as an extension. This will go in a PasswordValidationExtension in the same location as other extensions for the project. For each of the error messages, I am going to be using Kentico resource strings so they are easy to maintain if the site owners ever want to change them.

				
					public static class PasswordValidationExtension
{
    	public static bool ValidatePassword( this string password, out string errorMessage )
    	{
        	errorMessage = string.Empty;
        	bool valid = true;
        	if( string.IsNullOrWhiteSpace( password ) )
        	{
            	errorMessage = ResHelper.GetString( "MissingUpperChar" )
                               + Environment.NewLine + ResHelper.GetString( "MissingMinimumChar" )
                               + Environment.NewLine + ResHelper.GetString( "MissingNum" )
                               + Environment.NewLine + ResHelper.GetString( "MissingSpecialChar" );
            	return false;
        	}
 
        	var hasNumber = new Regex( @"[0-9]+" );
        	var hasUpperChar = new Regex( @"[A-Z]+" );
        	var hasMiniMaxChars = new Regex( @".{8,15}" );
        	var hasSymbols = new Regex( @"[!@#$%^&*()_+=\[{\]};:<>|./?,-]" );
        	if( !hasUpperChar.IsMatch( password ) )
        	{
         	   errorMessage += ResHelper.GetString( "MissingUpperChar" ) + Environment.NewLine;
            	valid = false;
        	}
 
        	if( !hasMiniMaxChars.IsMatch( password ) )
        	{
            	errorMessage += ResHelper.GetString( "MissingMinimumChar" ) + Environment.NewLine;
            	valid = false;
        	}
 
        	if( !hasNumber.IsMatch( password ) )
        	{
            	errorMessage += ResHelper.GetString( "MissingNum" ) + Environment.NewLine;
            	valid = false;
        	}
 
        	if( !hasSymbols.IsMatch( password ) )
        	{
            	errorMessage += ResHelper.GetString( "MissingSpecialChar" ) + Environment.NewLine;
            	valid = false;
        	}
 
        	return valid;
    	}
}


				
			

Defining the Validation Attribute

With the logic for the password validation out of the way, the attribute can be created to be used for the password validation. To make a class usable as an attribute, that class needs to inherit from the ValidationAttribute class. Then add the AttributeUsage attribute, and the types it will target. Attributes can target other types as well, including methods. This can be used to describe the method or prevent execution of the method, like the [ValidateAntiforgeryToken] or [AuthorizeAttribute]. Below is the PasswordValidationAttribute, which goes in a location for all custom validations in the project.

				
					[AttributeUsage( AttributeTargets.Field | AttributeTargets.Property )]
public class PasswordValidationAttribute : ValidationAttribute
{
    	public override bool IsValid( object value )
    	{
        	var valid = (( string )value).ValidatePassword( out string message );
        	ErrorMessage = message ?? string.Empty;
        	return valid;
    	}
 
    	public override string FormatErrorMessage( string name )
    	{
        	return string.Format(
            	CultureInfo.CurrentCulture,
                ErrorMessageString,
            	name );
    	}
}


				
			

For attributes that need to be applied multiple times, add AllowMultiple=true in the AttributeUsage decoration for the class being used as an attribute. This validation does not need to be applied multiple times to the same field, but below is what it would look like for an Attribute that would.

				
					[AttributeUsage( AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
				
			

Using the Validation Attribute

All that’s left now is to decorate the fields needing validation with the newly created Data Annotation. That annotation then validates the field and will pass the errors to the front end if there are any. The override for IsValid tells the attribute how to validate the field or property this Data Annotation is on. This is the method that needs to be overridden with custom logic when creating any custom validation. The override for FormatErrorMessage is only needed if the message will need to be formatted. The ErrorMessage property is inherited from the ValidationAttribute class, so setting this field on validation without overriding the FormatErrorMessage method will still work if you wish to define the error message in the Attribute itself.

				
					    	[PasswordValidation]
    	[Required]
    	public string Password { get; set; }
				
			

Validating Data

Any property with a Data Annotation will be validated automatically when a form is submitted with this property. Any errors that are caught in validation will invalidate the model state, allowing us to stop the submission before any other logic is run. We can then return the same view to the user, and the model state will contain the error messages needed to let the user know which form fields need attention. Now, a password can not be submitted into a form without having the four requirements from earlier. If any requirement is missing, the form is returned with a custom error message letting the user know which requirement their password did not meet.

This process can be used to validate any property. Change the name of the data annotation, change the logic for the validation, define the error message, and apply it to a property. Using a custom Data Annotation allows us to cover validations that are unique to an application. In this scenario, the validation could have been handled through a single Regex, but I needed more control over the error messages. With this validation, I am performing the same Regex match but updating the error specifically to what requirement(s) are missing. If my password meets 2 of the four requirements, the two missing requirements will be displayed in the returned form. This is less confusing for the user while still being clean in the backend.

About the Author

Brandon Dekker

When Brandon was offered a position at BizStream, he almost couldn’t believe it! He gets to do what he loves daily in a fun environment – this is his dream job! In his free time, he hangs out with his Destiny 2 clan, writes more code, and works on cars.

Subscribe to Our Blog

Stay up to date on what BizStream is doing and keep in the loop on the latest in marketing & technology.