Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecation of Transform and TransformForEach methods #2072

Closed
JeremySkinner opened this issue Feb 20, 2023 · 0 comments
Closed

Deprecation of Transform and TransformForEach methods #2072

JeremySkinner opened this issue Feb 20, 2023 · 0 comments

Comments

@JeremySkinner
Copy link
Member

JeremySkinner commented Feb 20, 2023

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.

@FluentValidation FluentValidation locked as resolved and limited conversation to collaborators Feb 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

1 participant