-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Summary
The Transform
and TransformForEach
methods are being deprecated in FluentValidation 11, and will be removed in FluentValidation 12.
Background
Since version 9, FluentValidation has supported transformations - the ability to apply a conversion to a property value before it's validated (for example, converting a string property to a nullable integer for the purposes of conversion). This feature has always been problematic as only simplistic transformations are supported, and there is no caching of the transformed value. Additionally this feature has always felt very much out of scope as it isn't directly related to validation.
As such we've decided to deprecate the Transform
and TransformForEach
methods and recommend that any transformations be done as computed properties within your model.
Migration
Option 1 - Use a computed property on the model (Recommended)
The example below uses a custom transformation method to transform a string to a nullable int. The updated version moves this logic into a computed property within the model itself.
// Model
class Foo
{
public string SomeValue { get; set; }
}
// Old behaviour, using the Transform method:
public class FooValidator : AbstractValidator<Foo>
{
public FooValidator()
{
Transform(x => x.SomeValue, to: StringToNullableInt).NotNull().GreaterThan(5);
}
int? StringToNullableInt(string value)
=> int.TryParse(value, out int val) ? (int?) val : null;
}
// After migration - the transformation takes place inside the model and is exposed via a computed property.
class Foo
{
public string SomeValue { get; set; }
public int? SomeValueAsInt => int.TryParse(SomeValue, out int val) ? val : null;
}
public class FooValidator : AbstractValidator<Foo>
{
public FooValidator()
{
RuleFor(x => x.SomeValueAsInt).NotNull().GreaterThan(5);
}
}
If calculating the transformed value is expensive and needs to be accessed many times then you could optionally cache it:
class Foo
{
private string _someValue;
public string SomeValue
{
get => _someValue;
set
{
_someValue = value;
if (int.TryParse(value, out var asInt))
{
SomeValueAsInt = asInt;
}
}
}
public int? SomeValueAsInt { get; private set; }
}
Option 2 - Perform the transformation within RuleFor (not recommended)
As an alternative, if it's not possible for you to change the definition of the model to include the computed property then you could continue to perform the transformation within the validator by performing the transformation within a call to RuleFor
rather than using the Transform
method:
public class FooValidator : AbstractValidator<Foo>
{
public FooValidator()
{
RuleFor(x => StringToNullalbleInt(x.SomeValue)).NotNull().GreaterThan(5)
.OverridePropertyName("SomeValue");
// Must include a call to OverridePropertyName as the property name can't be inferred with this approach.
}
int? StringToNullableInt(string value)
=> int.TryParse(value, out int val) ? (int?) val : null;
}
Note that this approach above is not recommended for the following reasons:
- Using a method call within a call to
RuleFor
means we can't cache the member accessor, making this rule more expensive to run than others - Using a method call within a call to
RuleFor
means we can't automatically compute the associated property name, so it has to be set explicitly with a call toOverridePropertyName
Because of the above reasons we don't recommend this approach and instead recommend using Option 1 - a computed property on the model.