Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Deprecation of Transform and TransformForEach methods #2072

Copy link
Copy link
@JeremySkinner

Description

@JeremySkinner
Issue body actions

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 to OverridePropertyName

Because of the above reasons we don't recommend this approach and instead recommend using Option 1 - a computed property on the model.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.