FluentValidation 8.2 released

FluentValidation 8.2 is now available. This release contains several new features and bug fixes:

ASP.NET Core DI Extensions for Child Validators

It’s always been possible to inject child validators when working with a Dependency Injection container such as ASP.NET Core’s Service Provider. Typically, you’d do this by injecting the child validator into the parent’s constructor and then calling SetValidator:

public class PersonValidator : AbstractValidator<Person> {
  public PersonValidator(AddressValidator addressValidator) {
    RuleFor(x => x.Address).SetValidator(addressValidator);
  }
}

With FluentValidation 8.2, you can optionally use the InjectValidator method instead:

public class PersonValidator : AbstractValidator<Person> {
  public PersonValidator() {
    RuleFor(x => x.Address).InjectValidator();
  }
}

In this case, FluentValidation will attempt to resolve an instance of IValidator<T> from ASP.NET’s service collection, where T is the same type as the property being validated (so in this case it’ll look for an implementation of IValidator<Address> registered with the container). If you need to explicitly specify the type, then this can be done with the other overload of InjectValidator which accepts a func referencing the service provider:

public class PersonValidator : AbstractValidator<Person> {
  public PersonValidator() {
    RuleFor(x => x.Address).InjectValidator((services, context) => services.GetService<SomeOtherAddressValidator>());
  }
}

Note that these methods are only available when using the automatic validation that’s part of FluentValidation’s ASP.NET Core integration. These methods will not be available to use if you’re invoking validators manually, or using FluentValidation outside of ASP.NET Core.

ASP.NET Core DI Extensions for registering validators

There are several new methods for registering validators with the ASP.NET service provider. These are extension methods on IServiceCollection:

AddValidatorsFromAssemblies(this IServiceCollection services, IEnumerable<Assembly> assemblies, ServiceLifetime lifetime = ServiceLifetime.Transient)AddValidatorsFromAssembly
AddValidatorsFromAssembly(this IServiceCollection services, Assembly assembly, ServiceLifetime lifetime = ServiceLifetime.Transient)
AddValidatorsFromAssemblyContaining(this IServiceCollection services, Type type, ServiceLifetime lifetime = ServiceLifetime.Transient)
AddValidatorsFromAssemblyContaining<T>(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Transient)

Typically you would not need to use these directly as the standard way of registering validators with the container would be to call the Register... methods as part of AddFluentValidation in application startup:

services.AddMvc().AddFluentValidation(fv => {
	fv.RegisterValidatorsFromAssemblyContaining<MyValidator>();
});

Internally, the methods part of AddFluentValidation now make use of the new service collection extensions.

RuleFor.ForEach as an alternative to RuleForEach.

FluentValidation has supported RuleForEach for several years, which allows a rule to be applied to all (or some) items in a collection. As an alternative to using RuleForEach, it’s now possible to call ForEach as part of a regular RuleFor. With this approach you can combine rules that act upon the entire collection with rules which act upon individual elements within the collection. For example:

RuleFor(x => x.Orders)
  .Must(x => x.Orders.Count <= 10).WithMessage("No more than 10 orders are allowed");

RuleForEach(x => x.Orders)
  .Must(order => order.Total > 0).WithMessage("Orders must have a total of more than 0");

The above 2 rules could be re-written as:

RuleFor(x => x.Orders)
  .Must(x => x.Orders.Count <= 10).WithMessage("No more than 10 orders are allowed")
  .ForEach(orderRule => {
    orderRule.Must(order => order.Total > 0).WithMessage("Orders must have a total of more than 0");
  });

I personally think that using 2 rules is clearer and easier to read, but the option of combining them is now available with the ForEach method.

Default interceptor implementation

It’s now possible to register a default implementation of IValidatorInterceptor with the service provider which will be used for all calls to FluentValidation by the automatic ASP.NET integration. See the documentation for more details on interceptors.

Note that this is only available with ASP.NET Core, not with MVC5 or WebApi2.

WithLocalizedMessage is deprecated

The overload of WithMessage that takes a callback/func should be used instead.

MVC5 and WebApi2 integration is deprecated

Support for MVC5 and WebApi2 is now considered legacy. Going forward, only the ASP.NET Core integration will receive updates. Note that the current MVC5 and WebApi2 packages will continue to work just fine as part of FluentValidation 8.x but won’t receive any further updates and won’t ship as part of 9.0.

Written on April 9, 2019

FluentValidation 8.1 released

FluentValidation 8.1 is now available. This release contains several new features and bug fixes:

String Formatting in Message Placeholders

FluentValidation has always supported placeholders within error messages, such as {PropertyValue}:

RuleFor(x => x.Age).GreaterThan(18).WithMessage("Must be older than 18 years. You entered {PropertyValue} years.");

Now you can also specify standard .NET formatting strings as part of the placeholder, such as {PropertyValue:d} or {PropertyValue:p1} etc.

Overriding Indexers for Collection Rules

Collection rules built using RuleForEach now allow you to override the indexer. For example, imagine you defined a collection rule against an collection of Address lines:

RuleForEach(x => x.AddressLines).NotNull();

…Then the validator would generate validation failures for properties person.AddressesLines[0] and person.AddressLines[1] etc. The indexer would always be the numeric index within the collection surrounded by square brackets. This can now be overridden to use something different, or remove the indexer completely. Eg:

v.RuleForEach(x => x.AddressLines)
  .OverrideIndexer((x, collection, element, index) => "<" + index + ">")
  .NotNull()

This would use angle brackets rather than square brackets in the generated indexer.

Error code can now be used to select a different default error message.

You can now use error codes to point to a particular error message stored in the default language manager. For example, the default ‘NotNull’ validation message has a code of NotNullValidator and a message of '{PropertyName}' must not be empty. If you want to use this message from inside a different validator (eg for a custom validator), you would have to hard-code the message again:

RuleFor(x => x.Name).NotNull(); //defaults to '{PropertyName}' must not be empty.
// `Must` has its own default error message, so if you want to use the NotNull message you'd have to specify it explicitly
RuleFor(x => x.Name).Must(name => name != null).WithMessage("'{PropertyName}' must not be empty.");

However now you can select a message based on error code:

RuleFor(x +> x.Name).Must(name => name != null).WithErrorCode("NotNullValidator"); 

Test Helper improvements

Additional details are now shown in the output for ShouldNotHaveValidationErrorFor.

Conditional validation improvements

If you use the top-level When or Unless methods to wrap multiple rules, the condition check is now cached and only executed once.

// The IsStudnet check will now only be executed once.
When(x => x.IsStudent, () => {
   RuleFor(x => x.StudentNumber).NotNull();
   RuleFor(x => x.Courses).NotNull();
});

There’s also a new Otherwise method that can now be chanined onto a When or Unless to do its opposite, without having to set up a second When call with the opposite condition:

When(x => x.IsStudent, () => {
   RuleFor(x => x.StudentNumber).NotNull();
   RuleFor(x => x.Courses).NotNull();
}).Otherwise(() => {
   RuleFor(x => x.StudentNumber).Null();
});

Other minor changes

  • MVC 5’s CustomizeValidatorAttribute now has the Skip property, like WebApi and AspNetCore, allowing auto-validation to be skipped for specific action parameters.
  • Japanese language translation
  • The overload of OverridePropertyName that takes an expression can now use an expression with any return value, not just strings.
  • Minor wording changes to the default English error messages.
Written on December 6, 2018

Updating .NET Core inside AppVeyor

I currently use appveyor to run Continuous Integration builds for FluentValidation. It’s really easy to get started with appveyor as the build images come pre-installed with most tools you’d want to use as part of a build. The downside is it’s not easy to specify which version of a particular tool you want to use, and appveyor often lags behind with new releases.

For example, for FluentValidation I want the master branch to be built against the current LTS version of the .NET Core SDK (2.1.400), and I want my vNext branch to build against 2.2-preview1. But appveyor only has version 2.1.300 installed, so both branches need a new version of the .NET SDK.

Based on this post by Andrew Lock, I updated the FluentValidation build script to download an install a newer .NET Core SDK if needed. The build script uses powershell with the posh-build helpers for defining targets:

target install-dotnet-core {
  # Ensures that .net core is up to date.
  # first get the required version from global.json
  $json = ConvertFrom-Json (Get-Content "$path/global.json" -Raw)
  $required_version = $json.sdk.version

  # Running dotnet --version stupidly fails if the required SDK version is higher 
  # than the currently installed version. So move global.json out the way 
  # and then put it back again 
  Rename-Item "$path/global.json" "$path/global.json.bak"
  $current_version = (dotnet --version)
  Rename-Item "$path/global.json.bak" "$path/global.json"
  Write-Host "Required .NET version: $required_version Installed: $current_version"

  if ($current_version -lt $required_version) {
    # Current installed version is too low.
    # Install new version as a local only dependency. 
    $urlCurrent = "https://dotnetcli.blob.core.windows.net/dotnet/Sdk/$required_version/dotnet-sdk-$required_version-win-x64.zip"
    Write-Host "Installing .NET Core $required_version from $urlCurrent"
    $env:DOTNET_INSTALL_DIR = "$path/.dotnetsdk"
    New-Item -Type Directory $env:DOTNET_INSTALL_DIR -Force | Out-Null
    (New-Object System.Net.WebClient).DownloadFile($urlCurrent, "dotnet.zip")
    Write-Host "Unzipping to $env:DOTNET_INSTALL_DIR"
    Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory("dotnet.zip", $env:DOTNET_INSTALL_DIR)
  }
}

Here I do the following:

  • Check which version of .net core is needed based on what’s defined in my global.json
  • Check the current .net core installed version (working around that dotnet --version fails if there’s a mismatch between required and expected version)
  • Download the new SDK and unzip it to a local directory
  • Update the environment PATH variable so that the new version is used whenever dotnet is called.

This script can then be called as part of the install section of your appveyor.yml.

The full example is available in the FluentValidation repository.

Written on August 28, 2018

FluentValidation 8.0 released

FluentValidation is out and available to download from Nuget. This is a major release with several breaking change, so please make sure you read the upgrade notes before upgrading.

Validating properties by path

You can now validate specific properties using a full path, eg:

validator.Validate(customer, "Address.Line1", "Address.Line2");

Validating a specific ruleset with SetValidator

Previously, if you defined a child validator with SetValidator, then whichever ruleset you invoked on the parent validator will cascade to the child validator. Now you can explicitly define which ruleset will run on the child:

RuleFor(x => x.Address).SetValidator(new AddressValidator(), "myRuleset");

AttrbiutedValidatorFactory has been moved to a separate package

The ValidatorAttribute and the AttributedValidatorFactory were typically used in MVC/WebApi projects to wire models to their validators. This is no longer recommended when usign AspNetCore as the built-in Service Provider is a better alternative. These classes can still be used by explicitly installing the FluentValidation.ValidatorAttribute package. Note this package is installed by default if you are using the legacy MVC5/WebApi integration rather than AspNetCore.

SetCollectionValidator is deprecated

RuleForEach provides a more flexible syntax for the same result.

Async changes

Internally, the asynchronous validation API has been cleaned up thanks to await\async. From a consumer’s point of view, the asynchronous methods should all continue to work as before with the exception of some methods that previously didn’t take a CancellationToken that now do.

The full changelog is available here

Written on August 16, 2018

Powershell SSH Connection manager

I’ve recently a connection manager for SSH connections to the posh-sshell project.

What is Posh-Sshell?

Posh-Sshell is a set of powershell scripts that making working with SSH agents and clients easier. These utilities were originally part of the posh-git project, but have been separated into a separate module in preparation for the Posh-Git 1.0 release.

Posh-Sshell can be downloaded from the powershell gallery by running the following in a powershell prompt:

Install-Module Posh-Sshell -Scope CurrentUser

Once installed, import the module with Import-Module Posh-Shell (you can add this to your powershell profile so you don’t need to run it every time you launch a new terminal).

Connection Manager

Since splitting the SSH functionality out of posh-git, I’ve been working on several new features the first of which is the Connection Manager.

The Connection Manager can be used to display a list of SSH connections as well as add/remove connections from the ~/.ssh/config file. By running Connect-Ssh, you’ll be presented with a list of connections stored in your .ssh/config file:

image

You can enter the number of the server into the prompt, and an SSH connection will be made to that server. If no username is specified in the configuration file, then you’ll also be prompted for a username.

Adding a New Connection

A new connection can be added by running Add-SshConnection. In its simplest form, it takes an alias and a URI, eg:

Add-SshConnection MyServer3 myserver.mydomain.com

You can also specify additional common properties such as the username, either by using the -User parameter or using the user@host syntax:

# These are both the same
Add-SshConnection MyServer3 myserver.mydomain.com -User jeremy
Add-SshConnection MyServer3 jeremy@myserver.mydomain.com

You can specify a custom key file by using -IdentityFile <path> as well as parameters for configuring an SSH tunnel with -LocalTunnelPort <port number> and -RemoteTunnelPort <port number>.

Additional parameters can also be specified by supplying a hashtable to the -AdditionalOptions parameter.

After running Add-SshConnection MyServer3 myserver.mydomain.com -User jeremy, your .ssh/config file will contain the new entry:

Host MyServer3
  HostName myserver.mydomain.com
  User jeremy

Removing an entry

Entries can be removed from the config file by running Remove-SshConnection <name>

Written on July 30, 2018