Open Source Web Application Framework for ASP.NET Core
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

7.1 KiB

//[doc-seo]
{
    "Description": "Learn how to integrate FluentValidation with ABP Framework for enhanced validation capabilities in your applications."
}

FluentValidation Integration

ABP Validation infrastructure is extensible. Volo.Abp.FluentValidation NuGet package extends the validation system to work with the FluentValidation library.

Installation

It is suggested to use the ABP CLI to install this package.

Using the ABP CLI

Open a command line window in the folder of the project (.csproj file) and type the following command:

abp add-package Volo.Abp.FluentValidation

Manual Installation

If you want to manually install;

  1. Add the Volo.Abp.FluentValidation NuGet package to your project:

    dotnet add package Volo.Abp.FluentValidation
    
  2. Add the AbpFluentValidationModule to the dependency list of your module:

[DependsOn(
    //...other dependencies
    typeof(AbpFluentValidationModule) //Add the FluentValidation module
    )]
public class YourModule : AbpModule
{
}

Using the FluentValidation

Follow the FluentValidation documentation to create validator classes. Example:

public class CreateUpdateBookDtoValidator : AbstractValidator<CreateUpdateBookDto>
{
    public CreateUpdateBookDtoValidator()
    {
        RuleFor(x => x.Name).Length(3, 10);
        RuleFor(x => x.Price).ExclusiveBetween(0.0f, 999.0f);
    }
}

ABP will automatically find this class and associate with the CreateUpdateBookDto on object validation.

Exposing the Rules in the API Definition

ABP creates the API definition from the data annotation attributes of the DTO properties. So, a DTO that is only validated by FluentValidation has no constraints in the API definition, and the client proxy generators can not see them. The Volo.Abp.Http.FluentValidation NuGet package adds the FluentValidation rules to the API definition, merged with the ones coming from the attributes.

Installation

Open a command line window in the folder of the project (.csproj file) that hosts your HTTP API and type the following command:

abp add-package Volo.Abp.Http.FluentValidation

If you want to manually install, add the Volo.Abp.Http.FluentValidation NuGet package to your project and add the AbpHttpFluentValidationModule to the dependency list of your module:

[DependsOn(
    //...other dependencies
    typeof(AbpHttpFluentValidationModule)
    )]
public class YourModule : AbpModule
{
}

AbpHttpFluentValidationModule already depends on AbpFluentValidationModule, so you don't need both.

Mapped Rules

The following rules are mapped:

FluentValidation rule API definition
NotNull(), NotEmpty() IsRequired
Length(min, max), MinimumLength(min), MaximumLength(max) MinLength, MaxLength (a zero bound is left out, see below)
Matches(...) Regex
GreaterThanOrEqualTo(...), GreaterThan(...) Minimum (+ MinimumIsExclusive)
LessThanOrEqualTo(...), LessThan(...) Maximum (+ MaximumIsExclusive)
InclusiveBetween(...), ExclusiveBetween(...) Minimum, Maximum (+ the exclusive flags)

MinimumIsExclusive and MaximumIsExclusive indicate whether the value can be equal to the bound. They are also filled from the Range attribute, so an exclusive bound is not lost when it is declared with an attribute.

A Range attribute that writes its limits as strings, like [Range(typeof(decimal), "1.5", "9.5")], reads them in the culture of the request unless it sets ParseLimitsInInvariantCulture. Set it, so that the limit means the same thing to the server and to the api definition on every request.

When a rule and an attribute constrain the same property, the stricter bound is used: the higher minimum and the lower maximum. When both bounds have the same value, the exclusive one is used. The exclusivity always comes from the bound that is used, so [Range(0, 100)] with GreaterThan(-5) results in an inclusive Minimum = 0. A non-numeric bound, like a Range attribute on a DateTime property, is kept as-is. An existing Regex is also kept, because a single value can not express two patterns that both have to match.

Rules That Are Not Mapped

The following rules are not mapped, because they don't apply to every instance of the DTO:

  • Rules under When(...) / Unless(...) (both the chained and the block form) and their async variants, because the same property can be required for one instance and optional for another.
  • Rules that only belong to a non-default rule set, because ABP validates with FluentValidation's default selector, which does not run them.
  • RuleForEach(...) rules, because they constrain the items of a collection rather than the collection property.
  • Comparisons on a property that is not a number, and comparisons against another property. Minimum and Maximum are numeric bounds, so the ordinal comparison of two strings can not be published there.

Rules That Are Not Fully Expressed

The following rules are not fully expressed in the API definition:

  • A zero length bound, from MaximumLength(0) or Length(0, 0), is not published. Every length rule has a Func<T, int> form that reports the same zero on the descriptor, so the two can not be told apart.
  • Rules that come from an Include(...) call are not published, because FluentValidation does not expose the included validator on its descriptor.
  • A validator of a derived DTO can not add rules to a property declared by its base class, because each type describes only its own properties.
  • A rule on a nested object, like RuleFor(x => x.Address.City), is not published either. The nested type is described on its own, with its own validator, and its model is shared by every DTO that uses it.
  • A validator of a closed generic DTO is not used, because the API definition describes the generic type definition, which is shared by all of its instantiations.
  • InclusiveBetween(...) and ExclusiveBetween(...) with their own IComparer<T> are only published when their bounds still read as an interval in the natural order. FluentValidation does not expose the comparer, so a rule that orders its values differently can not be recognised.
  • Matches(pattern, RegexOptions) publishes the pattern without the options. This is the one case where a client can be stricter than the server, so avoid the overload if the client should not reject what the server accepts.

The API definition describes a type, while the server runs the validation per action. So, a DTO that is only used as a return value, or that is sent to an action which doesn't validate its parameters, still declares its constraints here. This is also how the data annotation attributes have always been reported.

See Also