EPiServer Forms: Adding custom server side validation to form fields (Luhn algorithm)

When you’re adding custom EPiServer Forms field blocks to your solution, you occasionally have the need for adding custom validation as well. Here is a sample of how to add a custom server side validation to your textfield block; you will probably also need client side validation for this, please see the article EPiServer Forms: Adding custom client side validation to form field (Luhn algorithm).

Creds to my collegue Christer Bermar for his work on this one.

Creating a custom field validator for EPiServer Forms

EPiServer’s class ElementValidatorBase has all the methods that we will need to override. Validate for doing the actual validation, and BuildValidationModel for returning custom error messages back to the user.

LuhnValidator.cs

public class LuhnValidator : ElementValidatorBase
{
  public override bool? Validate(IElementValidatable targetElement)
  {
    return IsValidNumber(targetElement.GetSubmittedValue() as string);
  }

  public override IValidationModel BuildValidationModel(IElementValidatable targetElement)
  {
    var model = base.BuildValidationModel(targetElement);
    if (model != null)
    {
      var service = ServiceLocator.Current .GetInstance<ILocalizationServiceWrapper>();
      model.Message = service.GetString(TranslationKeys.Blocks .Forms.ValidationMessages.LuhnFail); 
    }
    return model;
  }

  private static bool IsValidNumber(string number)
  {
    if (string.IsNullOrEmpty(number))
    {
      return false;
    }
    if (number.Contains("-"))
    {
      number = number.Replace("-", string.Empty);
    }
    return Luhn.IsValid(number);
  }

The last line of code Luhn.IsValid(number); calls a method in a validation class created by Petros Kyladitis, which is included at the bottom along with licensing information as well as a source link to it’s location at BitBucket.

So to use the custom validator, just add it to your EPiServer Forms field block attribute as you would with EPiServer’s own validators.

[AvailableValidatorTypes(
  Include = new[]
  {
    typeof(RequiredValidator),
    typeof(LuhnValidator)
  }
)]

Should you for some reason wish to force the custom validation onto your custom block, preventing the web editors from making the choice themselves, this may be done by a simple override; please see article EPiServer Forms: Forcing a field block to use a certain validator for more information.

The Luhn algorithm is used for validation of for instance Swedish personal numbers, organisation numbers as well as credit card numbers.

Luhn validation by Petros Kyladitis

#region license
/* 
* Luhn - Version 1.0
* Copyright (c) 2012 - Petros Kyladitis
* All rights reserved.
* 
* This class provide validation checking for Luhn formula based numbers
* 
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: 
* 
* 1. Redistributions of source code must retain the above copyright notice, this
*    list of conditions and the following disclaimer. 
* 2. Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution. 
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

/// <summary>
/// This class provide static methods for validation checking of Luhn formula numbers.
/// Ref. https://bitbucket.org/multipetros/validation/src/e8f43334e067/Validation/Luhn.cs?fileviewer=file-view-default
/// </summary>
private static class Luhn
{
  /// <summary>
  /// The greek social id, uses the Luhn formula.<br />
  /// The last digit is the validation digit using the Luhn check digit algorithm.<ul><li>
  ///  1 - Counting from the check digit, which is the rightmost, and moving left, double the value of every second digit.</li><li>
  ///  2 - Sum the digits of the products (e.g., 10: 1 + 0 = 1, 14: 1 + 4 = 5) together with the undoubled digits from the original number.</li><li>
  ///  3 - If the total modulo 10 is equal to 0 (if the total ends in zero) then the number is valid according to the Luhn formula; else it is not valid.</li></ul>
  /// </summary>
  /// <param name="number">The social id number in string</param>
  /// <returns>True if pass the Luhn validation, else false</returns>
  public static bool IsValid(string number)
  {
    if (string.IsNullOrEmpty(number)) return false;

    var idLength = number.Length;
    var idSum = 0;
    var currentProcNum = 0; //the current process number (to calc odd/even proc)

    for (var i = idLength - 1; i >= 0; i--)
    {
      //get the current rightmost digit from the string
      var idCurrentRightmostDigit = number.Substring(i, 1);

      //parse to int the current rightmost digit, if fail return false (not-valid id)
      int currentDigit;
      if (!int.TryParse(idCurrentRightmostDigit, out currentDigit))
        return false;

      //bouble value of every 2nd rightmost digit (odd)
      //if value 2 digits (can be 18 at the current case),
      //then sumarize the digits (made it easy the by remove 9)
      if (currentProcNum % 2 != 0)
      {
        if ((currentDigit *= 2) > 9)
          currentDigit -= 9;
      }
      currentProcNum++; //increase the proc number

      //summarize the processed digits
      idSum += currentDigit;
    }

    //if digits sum is exactly divisible by 10, return true (valid), else false (not-valid)
    return (idSum % 10 == 0);
    }
  }
}