FluentValidation 9.2 and 9.3-preview1 released

FluentValidation 9.1 is now available. This release contains new features and performance improvements.

Inheritance Validator

FluentValidation 9.2 contains the new Inheritance Validator, which allows you to set up specific child validators for different subclasses/implementors depending on their runtime type.

For example, imaging the following example:

// We have an interface that represents a 'contact',
// for example in a CRM system. All contacts must have a name and email.
public interface IContact {
  string Name { get; set; }
  string Email { get; set; }
}

// A Person is a type of contact, with a name and a DOB.
public class Person : IContact {
  public string Name { get; set; }
  public string Email { get; set; }

  public DateTime DateOfBirth { get; set; }
}

// An organisation is another type of contact,
// with a name and the address of their HQ.
public class Organisation : IContact {
  public string Name { get; set; }
  public string Email { get; set; }

  public Address Headquarters { get; set; }
}

// Our model class that we'll be validating.
// This might be a request to send a message to a contact.
public class ContactRequest {
  public IContact Contact { get; set; }

  public string MessageToSend { get; set; }
}

Next we create validators for Person and Organisation:

public class PersonValidator : AbstractValidator<Person> {
  public PersonValidator() {
    RuleFor(x => x.Name).NotNull();
    RuleFor(x => x.Email).NotNull();
    RuleFor(x => x.DateOfBirth).GreaterThan(DateTime.MinValue);
  }
}

public class OrganisationValidator : AbstractValidator<Organisation> {
  public OrganisationValidator() {
    RuleFor(x => x.Name).NotNull();
    RuleFor(x => x.Email).NotNull();
    RuleFor(x => x.HeadQuarters).SetValidator(new AddressValidator());
  }
}

Now we create a validator for our ContactRequest. We can define specific validators for the Contact property, depending on its runtime type. This is done by calling SetInheritanceValidator, passing in a function that can be used to define specific child validators:

public class ContactRequestValidator : AbstractValidator<ContactRequest> {
  public ContactRequestValidator() {

    RuleFor(x => x.Contact).SetInheritanceValidator(v => {
      v.Add<Organisation>(new OrganisationValidator());
      v.Add<Person>(new PersonValidator());
    });

  }
}

Performance Improvements

FluentValidation 9.2 also contains several performance improvements which should make a difference if you’re invoking Validate on a large number of objects.

FluentValidation 9.3-Preview1 and .NET 5 support

FluentValidation 9.3-preview1 has also been released at the same time, which adds support for .NET 5 Preview 8.

Written on August 26, 2020

FluentValidation 9.1 released

FluentValidation 9.1 is now available. This release contains a few new features and some bug fixes. View the full release notes. A few highlights:

Simplifying overloads of Validate

FluentValidation has several overloads of its Validate method which have caused some confusion. For example, the overload that accepted a params array of property names has been in place since very early releases, which allowed for validating only specific properties with validator.Validate(instance, "Forename", "Surname")

However, this called ambiguity when we later introduced rule sets back in version 3.0 as you had to use a named parameter so as not to conflict with the property-name overload above:

validator.Validate(instance, ruleSet: "MyRuleSet");

If you forgot the explicit named parameter, the property names overload would be invoked instead. This became more confusing when we enabled validation of multiple rulsets, as you had to specify these with a comma-separated string:

validator.Validate(instance, ruleSet: "MyRuleSet,SomeOtherRuleSet");

On top of this confusion, there are various other overloads that allow specifying a validator selector, property names as expressions, and whether the validator should throw an exception or not, on top of the “core” validate method that did all the work (which takes a raw ValidatioCntext).

To try and tidy this up and avoid confusion, most of these overloads have now been deprecated and will be removed in FluentValidation 10. Instead, you can explicitly opt in to which features you want to use by passing in a set of configuration options:

// Validate specific rulesets
validator.Validate(instance, opt => {
  opt.IncludeRuleSets("MyRuleSet", "SomeOtherRuleSet");
  
  // Can also force rules not in a ruleset to be run
  opt.IncludeRulesNotInRuleSet();
});

// Validate specific properties
validator.Validate(instance, opt => {
  opt.IncludeProperties("Forename", "Surname");
  // or
  opt.IncludeProperties(x => x.Surname, x.Forename);
});

// Throw exceptions on failure
validator.Validate(instance, opt => {
   opt.ThrowOnFailures();
});

These options can be combined together in the same validation run eg:

// Validate specific rulesets
validator.Validate(instance, opt => {
  opt.IncludeRuleSets("MyRuleSet", "SomeOtherRuleSet").IncludeRulesNotInRuleSet();
  opt.ThrowOnFailures();
});

CascadeMode impprovements

FluentValidation has supported the concept of a “CascadeMode” since version 1.2. The cascade mode allows a rule to stop after the first failure. For example:

RuleFor(x => x.FirstName).Cascade(CascadeMode.StopOnFirstFailure).NotNull().NotEqual("foo");

In this case the NotEqual will not run if the NotNull fails. This logic has always been in place, and can also be set at the validator level:

public MyValidator() {
  this.CascadeMode = CascadeMode.StopOnFirstFailure;
  
  // The NotEqual on this rule will not run if the NotNull fails.
  RuleFor(x => x.FirstName).NotNull().NotEqual("foo");
  
  // The NotEqual on this will will also not run if its NotNull fails, 
  // but the NotNull will always run regardless of the rules on FirstName above
  RuleFor(x => x.LastName).NotNull().NotEqual("foo");
}

Setting this at the validator level is the equivalent of setting it against each of the rules. However, the behaviour was often misleading for users of the library. Many users have assumed this means “stop immediately if any rule produces a failure”, ie the whole validator could would only ever generate 0 or 1 validation errors. However this wasn’t the case, this actually meant each call to RuleFor would only ever generate 0 or 1 failures (as CascadeMode.StopOnFirstFailure only ever applies at the rule-level, even when set on the validator). This has caused a lot of confusion over the years.

To resolve this we decided to introduce a new behaviour which is consistent with what users were expecting, and allows for a true “fail-fast” behaviour. The new option (CascadeMode.Stop) will act as a true fail-fast. When set at the validator level:

public MyValidator() {
  // This whole validator class will only ever produce 1 failure at most.
  this.CascadeMode = CascadeMode.Stop;
  
  RuleFor(x => x.FirstName).NotNull().NotEqual("foo");
  RuleFor(x => x.LastName).NotNull().NotEqual("foo");
}

The existing StopOnFirstFailure option will remain unchanged for backwards compatibility, but has been marked as deprecated.

Async test helper

The test helper now has asynchronous overloads available.

View the full release notes to see other fixes.

Written on August 8, 2020

FluentValidation 9 released

FluentValidation 9 is now available. This release contains several new features and bug fixes, as well as some breaking changes. Please read the upgrade guide and the changelog for full details, but here are a few things to be aware of:

New TestHelper features

FluentValidation’s unit-testing functionality has been improved with several new advanced test extensions. Additional improvements here for use with asynchronous validators are planned for 9.1.

Improvements to ASP.NET Core setup

When auto-registering validators in ASP.NET Core applications, you can now optionally filter out types you don’t want to be registered:

services.AddMvc().AddFluentValidation(fv => {
  fv.RegisterValidatorsFromAssemblyContaining<MyValidator>(scanResult => {
    return scanResult.ValidatorType != typeof(SomeValidatorToExclude);
  });
});
	

NotEqual/Equal now perform ordinal comparisons

When using the Equal or NotEqual validators against a string property, these will now perform ordinal comparisons (rather than culture-specific comparisons). This fixes a regression that was introduced way back in FluentValidation 4. If you need to perform a culture-specific comparison, you must now explicitly specify this. See the API docs for details

Default Email Validation Mode

This is a subtle behaviour change. Prior to version 9.0 FluentValidation used a regular expression for email validation. This has been changed to a simple check for the presence of [something]@[something]. Although this is a more naive check, this brings FluentValidation’s email validation in-line with the .NET Core EmailAddressAttribute, and also follows the recommendation that regular expressions shouldn’t be used for email addresses. If you need to preserve the previous behaviour, you can supply an optional parameter to use the old behaviour: RuleFor(customer => customer.Email).EmailAddress(EmailValidationMode.Net4xRegex);

See here for more details on this change and the API documentation here

Updates to the ScalePrecision validator

The algorithm used by the ScalePrecision validator has been updated to match the behaviour of SQL Server (and other RDBMS solutions)

Removal of non-generic Validate overload and non-generic ValidationContext class

The IValidator.Validate(object model) overload has been removed to improve type safety. If you were using this method before, you should change your code to use the overload that accepts an IValidationContext instead:

var context = new ValidationContext<object>(model);
var result = validator.Validate(context);

Supported frameworks

Several old versions of the .NET Framework and .NET Core are no longer supported. The supported platforms list for the core FluentValidation library is now:

  • .NET Core 2.0 or newer
  • .NET Framework 4.6.1 or newer

For the ASP.NET integration, the supported platforms are:

  • .NET Core 3.1
  • .NET Core 2.1

If you need to run FluentValidation on older versions of .NET Framework (<4.6.1) or obsolete versions of .NET Core (2.0, 2.2, 3.0) then you’ll need to stick with FluentValidation 8.6.

Several changes to the internal API

This will not affect normal users of the library, but if you have built any custom advanced extensions then you should See the upgrade guide to check if they affect you

Removal of several deprecated features

Several features that were deprecated in FluentValidation 8.x have been removed in 9.0. See the upgrade guide for a complete list

Written on July 6, 2020

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