rskibbe.Validation – Validating .NET objects and values – but easy!

rskibbe.Validation - .NET Validation - but easy - Validating object models and values
rskibbe.Validation – .NET Validation – but easy – Validating object models and values

What is rskibbe.Validation?

rskibbe.Validation is a free NuGet package for C# and Visual Basic .NET apps, helping you validate „things“ and providing an easy to use and fluent interface. Basic things like „MaxLength“, „MinLength“, „NotNull“, etc. will help you validate your input. If you want even more functionality without creating things yourself, you can install the sub packages as well.

💡 In a hurry?: No problem, just navigate to the points of interest by using the table of contents from above. If you need a quick example, or an example download, scroll to the bottom.

Using it – in short

  1. Install the package
  2. Import namespaces
  3. Instantiate a new Validator
  4. Provide / Autodetect a TemplatePack implementation
  5. Load the TemplatePack where appropriate (like Form Load and such alike)
  6. Configure validation constraints / rules
  7. Call validate where appropriate (like in a submit button)
  8. Display the errors / handle success

How to use – in general?

Before we start using the rskibbe.Validation package itself, we will first talk about its core concepts. As already explained it will help you validating your forms / inputs. To do so, we will use the provided „Validator“ class.

Validator responsibility

One validator mostly has the responsibility for one entire formular, as you can see in the following image. You would typically create a private / protected field in the background / inside the form class and instantiate / configure it during form construction.

Validator Form Responsibility – rskibbe.Validation
Validator Form Responsibility – rskibbe.Validation

Constraint groups – the field specific rules

The Validator is pretty much useless, if you don’t tell „him“ what to do. So you need to configure some sort of „per field“ rules. Usually you could say something like: „The first name field shouldn’t be null, nor longer than 30 characters. But I also want that field to be required – having at least one character inside“. This is called a constraint group with different constraints.

Constraint Groups with Constraints – rskibbe.Validation
Constraint Groups with Constraints – rskibbe.Validation

The validation process itself

After creating the validator and the field specific rules itself, we can now talk about triggering the validation process. This is usually done by some sort of confirmation button, which will then create / update something in the database – whether it be through a repository, or not…

When pressing the button, call the validators „Validate“ method and it will iterate through each group and constraint specified. You can then check the return value of type „IValidationResult“ which contains information like „IsInvalid“ / „IsValid“ and the specific errors.

Triggering the validation process – rskibbe.Validation
Triggering the validation process – rskibbe.Validation

TemplatePacks

Version 1.1.1 introduced something called „TemplatePacks“. So far, the library provided you with basic messages like „shouldn’t be empty“ when (for example) using the „NotNull“ validation constraint. As assuming that everyone’s only using the english language would be pretty arrogant, I could’ve provided you with like every language – but this would be terrible as well. Even if it would be only a few strings for each language, this didn’t feel right – especially, when you have like 7100 spoken languages in the world.

That being said, I removed the default message template strings from the Extension methods like „NotNull“, „Length“, etc. and introduced the „TemplatePacks“. Just define your validator as usual and make sure to provide an „ITemplatePack“ implementation with the „UseTemplatePack“. In like the Form-Load code (or wherever suited), you could then load the contents of the „TemplatePack“. If you need more information, look at the more detailed section or examples.

A „GermanValidationTemplatePack“ and an „EnglishValidationTemplatePack“ are provided by default, because german is my main language and english is the world language. Other languages need to be implemented by you or maybe I will publish additional nuget packages later – but I honestly don’t know, if that isn’t too big.

Quick example

Quick example for rskibbe.Validation validating .NET objects and values
Quick example for rskibbe.Validation validating .NET objects and values

Let’s get started with the most basic example, to validate your objects and values in a .NET app. Feel free to scroll down, to get details about more complex scenarios.

Step 1 – Install the package(s)

Open the NuGet Package Manager and execute the following commands (or install it by GUI). We will prepare our app to use the most simple validation functionality – nothing special (which is shown further down):

Install-Package rskibbe.Validation

Step 2 – Import the corresponding namespace (s)

Depending on your custom setup, you need to choose the corresponding namespaces, which will enable using the provided classes and interfaces. This example will just use the easiest way possible:

using rskibbe.Validation.Models;
using rskibbe.Validation.Contracts;
Imports rskibbe.Validation.Models
Imports rskibbe.Validation.Contracts

Step 3 – Get a new validator class instance

To make use of a validator, you for sure need to create one, right!? Go ahead and create a validator like in one of these examples. I personally prefer the more visually chainable „Validator.New“ syntax:

// using the static factory property
// some prefer this for chaining
Validator.New

// using the usual way
new Validator()
' using the shared factory property
' some prefer this for chaining
Validator.New

' using the usual way
New Validator()

Step 3.1 – Specifying a TemplatePack

As mentioned in the Template Pack introduction above, since version 1.1.1 you need to provide a template pack implementation to avoid (either / or):

  • having to download every possible language
  • overriding each message template for each constraint / validator
  • only using the english language

You basically can either provide a „template pack“ with the use of the „UseTemplatePack“ method per instance, or you could for example use the static „GlobalTemplatePack“ property to provide a template pack at application startup. Each subsequently created validator will then use that template pack – when using the global method. Don’t forget to call „LoadTemplatePackAsync“ when suited – like in the „Form Load“ eventhandler. The validator will otherwise try to load the provided template pack when validating (which will slow down performance, etc.).

// provide an existing / prepared Template Pack
Validator
    .New
    .UseTemplatePack(myTemplatePack);
' provide an existing / prepared Template Pack
Validator _
    .New _
    .UseTemplatePack(myTemplatePack)

Autodetecting a Template Pack

Next to specifying a template pack manually, you can let the validator try to autodetect it. It will scan the executing assembly for an implementation of „ITemplatePack“. Keep an eye on the „iso“ parameter, which will allow you to pass something like „CultureInfo.CurrentUICulture.Name“ to get the current system language. The autodetect feature will then look for an implementation in the executing assembly like this:

// will autodetect an implementation
// you could also pass 'CultureInfo.CurrentUICulture.Name'
// to check for a specific language iso like "de-DE"
Validator
    .New
    .AutoDetectTemplatePack();
' will autodetect an implementation
' you could also pass 'CultureInfo.CurrentUICulture.Name'
' to check for a specific language iso like "de-DE"
Validator _
    .New _
    .AutoDetectTemplatePack()

Step 4 – Configure the validation rules

After having a validator, we now need to tell „him“ which values we want to be validated and which rules need to be applied to each of these values. Keep in mind that there will be scenarios where you will already have your values, but there will be other situations as well. Like when the user enters something in your UI and after pressing the „add“, „save“, „confirm“ button, you will only know the values at that exact moment.

That means, configuration and validation can (and will mostly) be 2 separated steps. In the easiest example, we will do both at once – this means no real „lazy“ evaluation of those values. If you want another example on this, you can take a look at the Windows Forms Example.

Checking one value with one or multiple rules

Let’s now define one value which should be validated with 2 typical rules like „shouldn’t be null and needs to be at least 1 character long, but not longer than 30 characters“. This is just for displaying purposes as you will mostly have more than one value to be checked – see the next example for this:

string myVarToCheck = "Robert";
Validator
    .New
    .For(myVarToCheck, "first name")
    .NotNull()
    .LengthBetween(1, 30);
Dim myVarToCheck = "Robert"
Validator _
    .New _
    .For(myVarToCheck, "first name") _
    .NotNull() _
    .LengthBetween(1, 30)

Checking multiple values with corresponding rules

Now we will take a look what like checking a first and a last name (or more…) would look like. As you will see in a few seconds, you can easily chain multiple „For’s“ creating more than one so called „ConstraintGroup“. So, the validator basically has one or multiple „constraint groups“ where each group will target checking the corresponding values.

string firstName = "Robert";
string lastName = "Skibbe";
Validator
    .New
    // the first "field" to validate
    .For(firstName, "first name")
    .NotNull()
    .LengthBetween(1, 30)
    // adding more
    .For(lastName, "last Name")
    .NotNull()
    .LengthBetween(1, 30);
Dim firstName = "Robert"
Dim lastName = "Skibbe"
Validator _
    .New _
    ' the first "field" to validate
    .For(firstName, "first name") _
    .NotNull() _
    .LengthBetween(1, 30)
    ' adding more
    .For(lastName, "last name") _
    .NotNull() _
    .LengthBetween(1, 30)

Step 5 – Checking the result

After we’ve finished the configuration and validation process, we can know take a look at the result. You can get the result of your configured constraint groups and provided values by calling „Validate“ on the validator instance.

💡 Keep in mind that (introduced in Version 1.1.1) there will be something called „TemplatePacks“. You can read the introduction at the top. The Validator will try to autodetect and load a „TemplatePack“ if not already specified when validating.

You can then check the results according properties like for example „IsInvalid“:

IValidationResult result = Validator
    .New
    // your rules and configurations
    .Validate();

if (result.IsInvalid)
{
    // do something..
    // early return;
}

// everything worked..
// save to db, etc.
Dim result As IValidationResult = Validator _
    .New _
    ' your rules and configurations
    .Validate()

If result.IsInvalid Then
    ' do something..
    ' early return
End If

' everything worked
' save to db, etc.

Complete quick example

Here you can find the complete quick example from the separated parts from above:

string firstName = "Robert";
string lastName = "Skibbe";
IValidationResult result = Validator
    .New
    // the first "field" to validate
    .For(firstName, "first name")
    .NotNull()
    .LengthBetween(1, 30)
    // adding more
    .For(lastName, "last Name")
    .NotNull()
    .LengthBetween(1, 30)
    .Validate();

if (result.IsInvalid)
{
    // do something..
    // early return;
}

// everything worked..
// save to db, etc.
Dim firstName = "Robert"
Dim lastName = "Skibbe"
Dim result As IValidationResult = Validator _
    .New _
    ' the first "field" to validate
    .For(firstName, "first name") _
    .NotNull() _
    .LengthBetween(1, 30)
    ' adding more
    .For(lastName, "last name") _
    .NotNull() _
    .LengthBetween(1, 30)
    .Validate()

If result.IsInvalid Then
    ' do something..
    ' early return
End If

' everything worked
' save to db, etc.

A typical Windows Forms example

Windows Forms Example - rskibbe.Validation
Windows Forms Example – rskibbe.Validation

After I showed you the quick example from above where we just combined configuration and validation in one step, we will now go further. In a real life scenario you would have like a UI where the user inputs some values, but this will happen at a point in time, where you already made your „validation configuration“. Getting the values to be validated will therefore be happening delayed / deferred.

Creating a simple form

You can now go ahead and create a simple windows forms application from scratch. We will stay with the created „Form1“ which should like the following. I just added 2 example input fields representing typical first and lastname fields like the ones from above.

You will then have this typical „confirm“ button, which should add or update the dataset in like the database. For sure, this should only happen, when everything is okay – or validated:

rskibbe.Validation - a simple Windows Forms example
rskibbe.Validation – a simple Windows Forms example

Configuring the rules for our fields

We will now create the basic configuration for our form fields. The configuration should happen and „stay alive“ as long as it’s needed. I would personally say, that this is the fact, as long as the form itself exists. During the existence of the form, the user could trigger the „ok“ multiple times, so we can’t only rely on one validation process.

Please create a configuration like the following – I will use the forms constructor for that. There will be 2 validation groups (one for each field) and 2 constraints / rules for each field.

Keep an eye on how I actually provided the „value“ to the „For“ method. I introduced a way how it can later pull the value, I didn’t provide the value itself at this point.

public Form1()
{
    InitializeComponent();
    Validator
        .New
        // create a new constraint group
        .For(() => tbFirstName.Text, "first name")
        // with two constraints
        .NotNull()
        .LengthBetween(1, 30)
        // another group
        .For(() => tbLastName.Text, "last name")
        // with the same constraints
        .NotNull()
        .LengthBetween(1, 30);
}
Sub New()
    InitializeComponent()
    Validator _
        .New _
        ' create a new constraint group
        .For(Function() tbFirstName.Text, "first name") _
        ' with two constraints
        .NotNull() _
        .LengthBetween(1, 30) _
        ' another group
        .For(Function() tbLastName.Text, "last name") _
        ' with the same constraints
        .NotNull() _
        .LengthBetween(1, 30)
End Sub

Storing the validator for later usage

Even though we now successfully configured our validator and the corresponding fields, we are not done yet. The main problem right now would be, that we can’t access the readily configured validator to actually trigger the validation process. The validation process needs to be triggered somewhere totally else – in the code of the confirmation button. To do so, we would need access to the configured instance there.

You can easily store the configured instance of the validator by just accessing the readonly „Validator“ property of one of the constraint groups. Do this after you’ve configured your validator:

// don't forget to import the namespaces

protected IValidator _validator;

public Form1()
{
    InitializeComponent();
    _validator = Validator
        .New
        // constraints config code
        // from the previous code example
        .Validator;
}
' don't forget to import the namespaces

Protected _validator As IValidator

Sub New()
    InitializeComponent();
    _validator = Validator _
        .New _
        ' constraints config code
        ' from the previous code example
        .Validator
End Sub

Checking the results on confirmation

After configuring everything and after the user has (potentially) entered something in our input fields, we can now check the results. As already mentioned, this will happen inside the „button click handler“-code. This could look like the following code snippet:

// don't forget to import the namespaces

// previous code

private void btnOk_Click(object sender, EventArgs e)
{
    IValidationResult result = _validator.Validate();
    if (result.IsInvalid)
    {
        // display something from "result"
        MessageBox.Show("Please check the input fields!");
        // early return
        return;
    }

    // create, update something in like the database
}
' don't forget to import the namespaces

' previous code

Private Sub btnOk_Click(sender As Object, e As EventArgs) Handles btnOk.Click
    Dim result As IValidationResult = _validator.Validate()

    If result.IsInvalid Then
        ' display something from "result"
        MessageBox.Show("Please check the input fields!")
        ' early return
        Return
    End If

    ' create, update something in like the database
End Sub

The complete Winforms example

If you followed the guide so far, but had problems implementing the single parts, you can easily integrate this into your app. Further down, you will find the download as well – which makes it even easier.

using rskibbe.Validation.Contracts;
using rskibbe.Validation.Models;

namespace ExampleGui;

public partial class Form1 : Form
{

    protected IValidator _validator;

    public Form1()
    {
        InitializeComponent();
        _validator = Validator
            .New
            // create a new constraint group
            .For(() => tbFirstName.Text, "first name")
            // with two constraints
            .NotNull()
            .LengthBetween(1, 30)
            // another group
            .For(() => tbLastName.Text, "last name")
            // with the same constraints
            .NotNull()
            .LengthBetween(1, 30)
            .Validator;
    }

    private void btnOk_Click(object sender, EventArgs e)
    {
        IValidationResult result = _validator.Validate();
        if (result.IsInvalid)
        {
            // display something from "result"
            MessageBox.Show("Please check the input fields!");
            // early return
            return;
        }

        // create, update something in like the database
    }

}
Imports rskibbe.Validation.Contracts
Imports rskibbe.Validation.Models

Namespace ExampleGui

    Public Partial Class Form1
        Inherits Form

        Protected _validator As IValidator

        Public Sub New()
            InitializeComponent()
            _validator = Validator _
                .New _
                .For(Function() tbFirstName.Text, "first name") _
                .NotNull() _
                .LengthBetween(1, 30) _
                .For(Function() tbLastName.Text, "last name") _
                .NotNull() _
                .LengthBetween(1, 30) _
                .Validator
        End Sub

        Private Sub btnOk_Click(sender As Object, e As EventArgs)
            Dim result As IValidationResult = _validator.Validate()

            If result.IsInvalid Then
                ' display something from "result"
                MessageBox.Show("Please check the input fields!")
                ' early return
                Return
            End If

            ' create, update something in like the database
        End Sub

    End Class

End Namespace

Avoiding / minimizing duplicate configurations

Avoiding minimizing duplicate repetitive definitions - rskibbe.Validation
Avoiding minimizing duplicate repetitive definitions – rskibbe.Validation

When thinking about creating different validation configurations, it’s common to have similar setups. Think for example about the following configuration which is pretty annoying. You need to duplicate the same stuff like 3 or more times:

Validator
    .New
    // field 1
    .For(() => tbFirstName.Text, "first name")
    .NotNull()
    .LengthBetween(1, 30)
    // same for field 2
    .For(() => tbMiddleName.Text, "middle name")
    .NotNull()
    .LengthBetween(1, 30)
    // same for field 3 - and so on...
    .For(() => tbLastName.Text, "last name")
    .NotNull()
    .LengthBetween(1, 30);
Validator _
    .New _
    ' field 1
    .For(Function() tbFirstName.Text, "first name") _
    .NotNull() _
    .LengthBetween(1, 30) _
    ' same for field 2
    .For(Function() tbMiddleName.Text, "middle name") _
    .NotNull() _
    .LengthBetween(1, 30) _
    ' same for field 3
    .For(Function() tbLastName.Text, "last name") _
    .NotNull() _
    .LengthBetween(1, 30)

To avoid repetitive code like the one displayed above, you can just use the „SameFor“ method on the chain. This way, you can define your configuration for one field and reuse the same config (or build upon) for other fields like the „middle name“ or „last name“ in this case.

Duplicating the last entry

With the following example, it will always duplicate the last configured entry. So the first call (in the next example) to „SameFor“ will duplicate the recent config of „first name“ and the second call to „SameFor“ will duplicate the config for the „middle name“ config. Keep reading the next section, to get an idea on how to duplicate the config from – not – the last one.

Validator
    .New
    .For(() => tbFirstName.Text, "first name")
    .NotNull()
    .LengthBetween(1, 30)
    .SameFor(() => tbMiddleName.Text, "middle name")
    .SameFor(() => tbLastName.Text, "last name");
Validator _
    .New _
    .For(Function() tbFirstName.Text, "first name") _
    .NotNull() _
    .LengthBetween(1, 30) _
    .SameFor(Function() => tbMiddleName.Text, "middle name") _
    .SameFor(Function() => tbLastName.Text, "last name")

Duplicating / extending from a specific config

Without specifying the „sourceKey“ parameter, it will automatically duplicate the most recent constraint group – with the new key. If you specify this parameter, you can pull a config from further above – not only the most recent one. This allows you to create more complex scenarios and won’t restrict you to always go for the latest config entry. Take a look at the following example:

Validator
    .New
    .For(() => tbFirstName.Text, "first name")
    .NotNull()
    .LengthBetween(1, 30)
    .SameFor(() => tbMiddleName.Text, "middle name")
    // I won't pull from middle name, I will pull from further up, "first name"
    .SameFor(() => tbLastName.Text, "last name", "first name");
Validator _
    .New _
    .For(Function() tbFirstName.Text, "first name") _
    .NotNull() _
    .LengthBetween(1, 30) _
    .SameFor(Function() => tbMiddleName.Text, "middle name") _
    ' I won't pull from middle name, I will pull from further up, "first name" _
    .SameFor(Function() => tbLastName.Text, "last name", "first name")

Specifying custom messages / templates

Custom messages and templating - rskibbe.Validation
Custom messages and templating – rskibbe.Validation

Starting with version 1.0.3 you can specify templates while configuring your constraints using the according methods. For the most templates you will need to specify the available placeholders – like the „max“ parameter considering the „MaxLength“ constraint method.

Default look

You can easily provide your custom messages / templates like in the following. Take a look how it would look by default – first:

// other code from the validator chain..
.LengthBetween(1, 30)
// other code from the validator chain..
' other code from the validator chain..
.LengthBetween(1, 30)
' other code from the validator chain..

The result:

Default LengthBetween message template
Default LengthBetween message template

Adjusting the template

Let’s now adjust that template you’ve seen above:

// other code from the validator chain..
.LengthBetween(1, 30, "at least {min} but not more than {max}")
// other code from the validator chain..
' other code from the validator chain..
.LengthBetween(1, 30, "at least {min} but not more than {max}")
' other code from the validator chain..

And it will now look like this:

Adjusted LengthBetween message template
Adjusted LengthBetween message template

Displaying / formatting the result

Formatting and displaying results - rskibbe.Validation
Formatting and displaying results – rskibbe.Validation

We talked about the principles and constraints and we’ve also talked about storing the instance of the validator to – for example – use the validate method. The validate method returns a result, which we can then check upon. Here you can see a typical example on how to display some custom errors or formatting those.

Using manual formatting

    private void btnOk_Click(object sender, EventArgs e)
    {
        // config further above!
        IValidationResult result = _validator.Validate();
        if (result.IsInvalid)
        {
            // display something from "result"
            var sb = new StringBuilder();
            foreach (var errorGroup in result.ErrorGroups)
            {
                sb.AppendLine($"{errorGroup.Key}");
                foreach (var error in errorGroup.Errors)
                    sb.AppendLine($"- {error.Message}");
                sb.AppendLine();
            }
            var nl = Environment.NewLine;
            MessageBox.Show($"Something went wrong: {nl}{nl}{sb}");
            // early return
            return;
        }

        // do the insert, update, whatever
    }
Private Sub btnOk_Click(sender As Object, e As EventArgs)
    ' your config further above!

    Dim result As IValidationResult = _validator.Validate()

    If result.IsInvalid Then
        Dim sb = New StringBuilder()

        For Each errorGroup In result.ErrorGroups
            sb.AppendLine($"{errorGroup.Key}")

            For Each err In errorGroup.Errors
                sb.AppendLine($"- {err.Message}")
            Next
            sb.AppendLine()
        Next

        Dim nl = Environment.NewLine
        MessageBox.Show($"Something went wrong: {nl}{nl}{sb}")
        ' early return
        Return
    End If

    ' do the insert, update, whatever
End Sub

The code from above will yield something like the following:

Displaying and formatting the results - rskibbe.Validation
Displaying and formatting the results – rskibbe.Validation

Using the ValidationResultFormatter class

If you don’t want to build up the string yourself, I added an example formatter class called „ValidationResultFormatter“ to construct the string from above. This way, you don’t need to manually build up from the groups and errors and you can choose where to display the message.

    private void btnOk_Click(object sender, EventArgs e)
    {
        IValidationResult result = _validator.Validate();
        if (result.IsInvalid)
        {
            // display something from "result"
            var nl = Environment.NewLine;
            var sb = new ValidationResultFormatter()
                .Format(result);
            MessageBox.Show($"Something went wrong: {nl}{nl}{sb}");
            // early return
            return;
        }

        // do the insert, update, whatever
    }
Private Sub btnOk_Click(sender As Object, e As EventArgs)
    Dim result As IValidationResult = _validator.Validate()

    If result.IsInvalid Then
        ' display something from "result"
        Dim nl = Environment.NewLine
        Dim sb = New ValidationResultFormatter().Format(result)
        MessageBox.Show($"Something went wrong: {nl}{nl}{sb}")
        ' early return
        Return
    End If

    ' do the insert, update, whatever
End Sub

Conditional checks

Conditional checking of constraints - rskibbe.Validation
Conditional checking of constraints – rskibbe.Validation

Sometimes you don’t want all constraints to be invoked. For example, if you have a customer input form, you could possibly want to distinguish between business and private customers. In this case you could go like this: I want the form to either be filled with a company name or first and last name – not both are required.

Currently the „IgnoreConditions“ are not copied when duplicating a „ConstraintGroup“ with like „SameFor“!

This could be done like this here:

// without comments
IValidator validator = Validator
    .New
    .For(() => tbFirstName.Text, "First name")
    .NotNull()
    .LengthBetween(1, 30)
    .Ignore(If.Provided(() => tbCompany.Text))
    .SameFor(() => tbLastName.Text, "Last name")
    .Ignore(If.Provided(() => tbCompany.Text))
    .SameFor(() => tbCompany.Text, "Company")
    .Ignore(If.Provided(() => tbFirstName.Text, () => tbLastName.Text))
    .Validator;

// with explanation comments
IValidator validator = Validator
    // kick off a new validator
    .New
    // create the first constraint group
    .For(() => tbFirstName.Text, "First name")
    // configure the constraints
    .NotNull()
    .LengthBetween(1, 30)
    // these previous rules for "First name" are ignored, when company is provided at evaluation
    .Ignore(If.Provided(() => tbCompany.Text))
    // copy the same constraints for "Last name" (but not the ignore rules!)
    .SameFor(() => tbLastName.Text, "Last name")
    // ignore this "last name" field as well, if company is provided at evaluation
    .Ignore(If.Provided(() => tbCompany.Text))
    // create the same basic constraints for company
    .SameFor(() => tbCompany.Text, "Company")
    // ignore company validation, if the first name and the last name is provided at evaluation
    .Ignore(If.Provided(() => tbFirstName.Text, () => tbLastName.Text))
    // get the configured validator instance
    .Validator;
' without comments
Dim validator = Validator
    .New _
    .For(Function() tbFirstName.Text, "First name") _
    .NotNull() _
    .LengthBetween(1, 30) _
    .Ignore(If.Provided(Function() tbCompany.Text)) _
    .SameFor(Function() tbLastName.Text, "Last name") _
    .Ignore(If.Provided(Function() tbCompany.Text)) _
    .SameFor(Function() tbCompany.Text, "Company") _
    .Ignore(If.Provided(Function() tbFirstName.Text, Function() tbLastName.Text)) _
    .Validator

' with explanation comments
Dim validator = Validator
    ' kick off a new validator
    .New
    ' create the first constraint group
    .For(Function() tbFirstName.Text, "First name")
    ' configure the constraints
    .NotNull()
    .LengthBetween(1, 30)
    ' these previous rules for "First name" are ignored, when company is provided at evaluation
    .Ignore(If.Provided(Function() tbCompany.Text))
    ' copy the same constraints for "Last name" (but not the ignore rules!)
    .SameFor(Function() tbLastName.Text, "Last name")
    ' ignore this "last name" field as well, if company is provided at evaluation
    .Ignore(If.Provided(Function() tbCompany.Text))
    ' create the same basic constraints for company
    .SameFor(Function() tbCompany.Text, "Company")
    ' ignore company validation, if the first name and the last name is provided at evaluation
    .Ignore(If.Provided(Function() tbFirstName.Text, Function() tbLastName.Text))
    ' get the configured validator instance
    .Validator

Creating custom constraints

There will be some scenarios where you could want to add more constraints to the existing ones. This can currently be done by just creating your two custom classes:

  • one for the constraint itself
  • and the other class will be the error template.

Creating a Constraint class

As mentioned, you need to create a custom constraint class first. For this, you will inherit from the „ValidationConstraint“ class and provide a constructor with a custom implementation of the „Validate“ function. The „Validate“ function is the core thing here as this will determine, if your custom validation succeeds or fails:

    public class MyConstraint : ValidationConstraint
    {

        public MyConstraint(string key, string template) : base(key, new MyValidationErrorMessageTemplate(key, template))
        {
            
        }

        public override bool Validate(object? value)
        {
            return false;
        }

    }
Public Class MyConstraint
    Inherits ValidationConstraint

    Public Sub New(key As String, template As String)
        MyBase.New(key, New MyValidationErrorMessageTemplate(key, template))
    End Sub

    Public Overrides Function Validate(value As Object?) As Boolean
        Return False
    End Function

End Class

Adding the error message template

After defining your own constraint class, you now need to create a corresponding error message template class. You can do so by inheriting from one of two classes:

  • the ValidationErrorMessageTemplate class
  • or the MyValidationErrorMessageTemplate class

As the names already suggest: One class is for templates without placeholders and the other one is used for templates with placeholders. I will show the easy example without placeholders in this case. If you want to use placeholders for your error message template, just inherit from the class mentioned above and override the abstract „SetupPlaceholders“ method to add your placeholders to the „Placeholders“ list property.

    public class MyValidationErrorMessageTemplate : ValidationErrorMessageTemplate
    {

        public MyValidationErrorMessageTemplate(string key, string template) : base(key, template)
        {

        }

    }
Public Class MyValidationErrorMessageTemplate
    Inherits ValidationErrorMessageTemplate

    Public Sub New(key As String, template As String)
        MyBase.New(key, template)
    End Sub

End Class

Adding the custom constraint

In the last step, we will add our created constraint to our „validation chain / list“. Otherwise our constraint will not be used:

Validator
    .New
    .For(() => tbFirstName.Text, "First name")
    .NotNull()
    .LengthBetween(1, 30)
    .AddConstraint(new MyConstraint("First name", "The error message"));
Validator
    .New _
    .For(Function() tbFirstName.Text, "First name") _
    .NotNull() _
    .LengthBetween(1, 30) _
    .AddConstraint(New MyConstraint("First name", "The error message"))

Translating the messages

I will work more on translations within the next versions as I’m trying to be as easy and compatible to my other NuGet packages as well. Until then it should be already possible by just creating the validator on language change with the fitting translations.

Downloads

Reading can sometimes be pretty annoying, right? I got you covered here, you can easily download the example project of your choice. This way, you will be „ready to go“ in a few seconds and you can see my package in action. If you have problems with the NuGet packages – make sure to reinstall the needed packages. Before uploading, I remove every binary file, so that every receives a „no binary“ version to build it themselves.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert