Skip to content

Document Validation

Anton Chaplygin edited this page Sep 16, 2019 · 6 revisions

Getting started

The Mutators library provides a way to define document validations. By a document we mean a C# class with some fileds and/or properties that in turn can be classes, structures, arrays or basic C# language types. For example:

public class UserData
{
    public string UserId { get; set; }

    public ContactInfo ContactInfo { get; set; }

    public string[] UserGroupIds { get; set; }
}

public class ContactInfo
{
    public string Email { get; set; }

    public string Phone { get; set; }
}

In order to use Mutators validation mechanics we should create a class derived from the abstract class DataConfiguratorCollectionBase<TData> with the UserData class as it's generic type parameter. Then we should provide an implementation for the abstract method Configure. In this method you can set validation rules using the MutatorsConfigurator parameter.

public class UserDataConfiguratorCollection : DataConfiguratorCollectionBase<UserData>
{
    public UserDataConfiguratorCollection(IDataConfiguratorCollectionFactory dataConfiguratorCollectionFactory,
                                          IConverterCollectionFactory converterCollectionFactory,
                                          IPathFormatterCollection pathFormatterCollection)
        : base(dataConfiguratorCollectionFactory, converterCollectionFactory, pathFormatterCollection)
    {
    }

    protected override void Configure(MutatorsContext context, MutatorsConfigurator<UserData> configurator)
    {
        configurator.Target(x => x.UserId)
                    .Required()
                    .NotLongerThan(18);

        var contractInfoConfigurator = configurator.GoTo(x => x.ContactInfo);

        contractInfoConfigurator.Target(x => x.Email)
                                .InvalidIf(x => !x.Email.Contains("@"),
                                           x => new InvalidEmailText())
                                .RequiredIf(x => string.IsNullOrEmpty(x.Phone));

        configurator.Target(x => x.UserGroupIds.Each())
                    .InvalidIf(x => x.UserGroupIds.Count(id => x.UserGroupIds.Current() == id) > 1, 
                               x => new UserGroupIdMustBeUniqueText());
    }
}

We use the configurator's method Target to specify a path to a property that we want to validate. After the Target method we can call one or more validation methods such as RequiredIf and InvalidIf. You can see all available validation methods in the MutatorsConfiguratorExtensions or you can implement your own validation method. These methods take a lambda expression from TData to bool? as a condition parameter. If the condition is satisfied, then the corresponding validation result will have type 'Error', if the condition is not satisfied, then the result will have type 'Ok'.

If a target property is absent then no validation result will be produced.

How to get a validation function

The DataConfiguratorCollectionBase class exposes the public method GetMutatorsTree, which returns the MutatorsTreeBase<TData> class. Meaning of this class is not of our interest at the moment, it is suffice to say that this class is an intermediate representation of the validations we set and we can obtain a validation function from it by calling the GetValidator method.

Validation result

The validation function returned by the GetValidator method returns a ValidationResultTreeNode value. It follows that the result of a document validation is a tree reflecting the document structure and the GetValidator method returns the root of that tree. Each node of the tree contains the result of all validations bound to this node. You don't need to worry about the validation result tree because ValidationResultTreeNode implements C# iterator pattern, so you can obtain the IEnumerable<FormattedValidationResult>. The FormattedValidationResult class has properties Type, Message, Path and other properties with information about executed validations.

Goto method

Goto is a method which helps you get rid of a boilerplate code. Instead of writing long and slightly different paths to properties from the root of the document, you can travel to a node of the document tree and define validation rules for the subtree of this node. The Goto method returns a new MutatorsConfigurator instance, which "remembered" a path to a node of the document tree. All configurator methods called on the new instance will take as parameters paths going from the corresponding document subtree root.

Array item validation

Mutators give you ability to set validations for array items (see the last validation in the example above). To provide a path to an array item you can use the method Each called on an array property. It also allows you to use the Current method on an array property inside a condition of a validation method, which means that you accessing the same index of the array as the index of the array in the Target method (as in the example above).

Validation message

Some validation methods (for example the InvalidIf method) require a message parameter, which is a lambda expression returning a class inherited from the MultiLanguageTextBase class. This class is meant to provide a validation message for multiple languages, you can see ValueMustBeEqualToText as an example.

MutatorsContext

The MutatorsContext is a parameter of the Configure method, which is used to customize a validation ruleset. See more on MutatorsContext page.

If method

The If method takes a condition (represented by a lambda expression) and returns a new MutatorsConfigurator instance which "remembered" this condition. It will be added (using logical 'and') to all conditions of validations that was set using this instance (or instances created from this instance). You can use If method in case when you need to set multiple validations that have some common part in their conditions.

DataConfiguratorCollection constructor parameters

DataConfiguratorCollectionFactory

This parameter is not required for simple document validation, you can pass null as the corresponding constructor parameter. See more on [DataConfiguratorCollectionFactory] page.

ConverterCollectionFactory

This parameter is not required for simple document validation, you can pass null as the corresponding constructor parameter. See more on [ConverterCollectionFactory] page.

PathFormatterCollection

The IPathFormatterCollection interface encapsulates a fabric that will be called to obtain the IPathFormatter. IPathFormatter is an abstraction for a class that generates a code which will be executed to build a path to a property under validation.

You can simply pass a new PathFormatterCollection instance to the DataConfiguratorCollection constructor. It will use SimplePathFormatter class for any type you validate. It concatenates names of properties in a path with dots (.) and uses square brackets to denote array indices ([index]), for example: UserData.UserGroupIds[2].