Table of Contents

Creating an entity view validation rule

In this article, you will learn how to create an entity view validation rule that checks the validity of a product's Ean13 code. This rule will check the code format and its checksum digit.

Prerequisite

This article assumes that you have followed the previous quick start guide chapters and that you are familiar with Visual Studio and xUnit.

Before starting, you must add a new string property named Ean13 on the Product entity and restart the metadata generation process from this entity.

Creating the validation rule metadata and generating the implementation class

Add a validation rule with the following properties on the ProductView entity view:

Validation rule name = Ean13FormatValidation

Save the changes and press the Visual Studio button.

This save generates the C# class and its unit test class:

modules
└───Catalog
    └───businessAssembly
        └───Application
        │   └───ValidationRules
        │       │   Ean13FormatValidation.cs
        └───Application.Tests.UnitTests
            └───ValidationRules
                │   Ean13FormatValidationTests.cs

Review of the generated code

This file Ean13FormatValidation.cs contains the implementation of the validation rule. The default implementation is just an example and must be modified.

public class Ean13FormatValidation : ValidationRule<IProductView>, IEan13FormatValidation
{
    private readonly INeosLogger<IEan13FormatValidation> _logger;

    /// <summary>
    /// Initializes a new instance of the <see cref="Ean13FormatValidation"/> class.
    /// </summary>
    /// <param name="logger">Logger.</param>
    public Ean13FormatValidation(INeosLogger<IEan13FormatValidation> logger)
    {
        _logger = logger;
    }

    /// <inheritdoc/>
    public override IValidationRuleResult Execute()
    {
        _logger.LogWarning("Ean13FormatValidation failed");
        return Error("<Put your error message here>");
    }
}

This file Ean13FormatValidationTests.cs contains the unit tests for the validation rule. The default implementation is just an example and must be modified.

public async Task ValidationShouldWork()
{
    // Arrange
    IProductView item = GetEntityViewRepository().AddNew();

    // Act
    IValidationRuleResult result = await ExecuteValidationRuleAsync(item);

    // Assert
    result.IsSuccess.Should().BeTrue();
}

Validation rule implementation

You will follow a TDD process to implement the validation rule.

Basic implementation

Update the test to execute the validation with a valid Ean13. To do this, modify the line that defines the value of the Ean13 property on the entity view :

item.Ean13 = "3130630555094";

Now the test fails when you pass a valid Ean13 code.

Change the validation rule implementation to make this first test case pass. In order to keep things simple, modify the Execute method to match the following code:

///<inheritdoc/>
public override IValidationRuleResult Execute()
{
    return Success();
}

With this new version, the test succeeds when receiving a valid Ean13 code.

Warning

Please don't stop there, this code is certainly not robust enough to go into production :-).

Length control implementation

You now need a test to make sure that the validation rule fails when the entity view has an Ean13 code that is not 13 characters long. You could write another test method but you will use a xUnit theory instead. A theory is a test method that takes parameters. In our case, our parameters are the Ean13 code to validate and the expected result. Replace the test method with the following theory:

[Theory]
[InlineData("3130630555094", true)]
public async Task ValidationShouldWork(string? ean13, bool expectedResult)
{
    // Arrange
    IProductView item = GetEntityViewRepository().AddNew();
    item.Ean13 = ean13;

    // Act
    IValidationRuleResult result = await ExecuteValidationRuleAsync(item);

    // Assert
    result.IsSuccess.Should().Be(expectedResult);
}

Add a new InlineData attribute providing a new set of parameters:

[InlineData("12345678", false)]

This attribute asserts that when the Ean13 code 12345678 is provided to the validation rule, the expected result is false. For the moment, the test fails for this case.

Update the validation rule in order to make this test pass:

///<inheritdoc/>
public override IValidationRuleResult Execute()
{
    if (Item.Ean13 != null && Item.Ean13.Length != 13)
    {
        return Error(string.Format("The ean13 {0} is invalid.", Item.Ean13));
    }

    return Success();
}
Warning

To make this article more readable, the error message is directly placed in the code. In a real project, it must be stored in a resource file.

Checksum digit check implementation

You will add a new test to make sure that the validation rule fails if the checksum digit is invalid.

Add an InlineData attribute to provide a new set of data containing an Ean13 code with a correct length but an invalid checksum digit:

[InlineData("4656952659845", false)]

Currently, this test scenario passes when it shouldn't. Change the validation rule code to add a very basic checksum digit check:

///<inheritdoc/>
public override IValidationRuleResult Execute()
{
    if (Item.Ean13 != null)
    {
        if (Item.Ean13.Length != 13)
        {
            return Error(string.Format("The ean13 {0} is invalid.", Item.Ean13));
        }

        // Calculate checksum digit
        int[] a = new int[12];
        a[0] = int.Parse(Item.Ean13[0].ToString());
        a[1] = int.Parse(Item.Ean13[1].ToString()) * 3;
        a[2] = int.Parse(Item.Ean13[2].ToString());
        a[3] = int.Parse(Item.Ean13[3].ToString()) * 3;
        a[4] = int.Parse(Item.Ean13[4].ToString());
        a[5] = int.Parse(Item.Ean13[5].ToString()) * 3;
        a[6] = int.Parse(Item.Ean13[6].ToString());
        a[7] = int.Parse(Item.Ean13[7].ToString()) * 3;
        a[8] = int.Parse(Item.Ean13[8].ToString());
        a[9] = int.Parse(Item.Ean13[9].ToString()) * 3;
        a[10] = int.Parse(Item.Ean13[10].ToString());
        a[11] = int.Parse(Item.Ean13[11].ToString()) * 3;
        int sum = a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] + a[9] + a[10] + a[11];
        int check = (10 - (sum % 10)) % 10;

        // Evaluate checksum digit
        int last = int.Parse(Item.Ean13[12].ToString());

        if (check != last)
        {
            return Error(string.Format("The {0} Ean13 code is invalid.", Item.Ean13));
        }
    }

    return Success();
}

All the test scenarii now pass.

We stop here with this example, but feel free to continue creating test methods or scenarii and refactoring the code to make it more robust. For example, you can try adding test with 465X952659845 Ean13 code (spoiler: it will not go well!).

Validation rule execution

To test your validation rule in the user interface, generate the application with the following command in the cluster folder:

neos generate

Once the application is generated, launch it with the following command:

neos run

You can test the rule in the product edit page. The validation rule should fire at saving and work as intended.

Updating the validation rule while the application is running

It is possible to update your validation rule without having to exit the application.
For example, you can add a line to log a message when the checksum digit is invalid:

///<inheritdoc/>
public override IValidationRuleResult Execute()
{
    if (Item.Ean13 != null)
    {
        // ...
        int check = (10 - (sum % 10)) % 10;

        // Evaluate checksum digit
        int last = int.Parse(Item.Ean13[12].ToString());

        if (check != last)
        {
            _logger.LogInformation("the {Ean13} Ean13 code checksum digit is invalid, the expected value is {ExpectedCheckDigit}", Item.Ean13, check );
            return Error(string.Format("The {0} Ean13 code is invalid.", Item.Ean13));
        }
    }

    return Success();
}

Compile this new version, go back to the web page, set the Ean13 code to 3130630555093 and save. The following message should appear in the log window:

15:41:31 INF Proxy/Northwind/Back/Northwind.Application.Abstractions.Rules.ValidationRules.ProductView.IEan13FormatValidation - the "3130630555093" Ean13 code checksum digit is invalid, the expected value is 4