Start Debugging

FluentValidation 12.2 corrige o crash de IsInEnum() em apps native AOT com enums [Flags]

O FluentValidation 12.2.0, lançado em 10 de outubro de 2026, troca Enum.GetValues por Enum.GetValuesAsUnderlyingType dentro do EnumValidator, então IsInEnum() em um enum [Flags] não lança mais NotSupportedException em um binário native AOT.

O FluentValidation 12.2.0 chegou ao NuGet em 10 de outubro de 2026. A maior parte do changelog é manutenção (mensagens em lituano e bielorrusso, algumas correções em PropertyChain), mas uma linha importa muito se você publica com PublishAot: “Change EnumValidator to work under native AOT”. Antes desta versão, RuleFor(x => x.Access).IsInEnum() em um enum [Flags] compilava, passava em todos os testes baseados em JIT e depois derrubava o binário nativo publicado na primeira chamada de validação.

Como era o crash

Aqui está um app mínimo. É o formato que você encontraria em qualquer minimal API publicada com AOT que valida um enum de permissões ou opções:

using FluentValidation;

var v = new GrantValidator();
Console.WriteLine(v.Validate(new Grant(Access.Read | Access.Write)).IsValid);
Console.WriteLine(v.Validate(new Grant((Access)64)).IsValid);

[Flags]
public enum Access { None = 0, Read = 1, Write = 2, Delete = 4 }

public record Grant(Access Access);

public class GrantValidator : AbstractValidator<Grant>
{
    public GrantValidator() => RuleFor(x => x.Access).IsInEnum();
}

Publiquei com <PublishAot>true</PublishAot> no SDK do .NET 10.0.302 (osx-arm64), uma vez com o FluentValidation 12.1.1 e outra com o 12.2.0. Com o 12.1.1 o binário morre imediatamente:

Unhandled exception. System.NotSupportedException: 'Access[]' is missing native code or metadata.
   at System.Array.CreateInstance(Type, Int32) + 0x5c
   at System.RuntimeType.GetEnumValues() + 0x88

Com o 12.2.0 o mesmo código imprime True e False, que é a resposta correta para uma combinação válida e um bit não definido.

Por que só enums [Flags] eram afetados

O EnumValidator segue dois caminhos. Enums sem flags passam por Enum.IsDefined, que sempre foi seguro para AOT. Enums com flags passam por um método privado EvaluateFlagEnumValues que monta uma máscara a partir de cada valor declarado, e no 12.1.x ele fazia isso com Enum.GetValues(Type). Essa sobrecarga é marcada com [RequiresDynamicCode] porque precisa alocar um Access[] em runtime, e o ILC não consegue pré-gerar um tipo de array que nunca viu estaticamente.

A publicação até avisava, só que não em voz alta. Por padrão, o trimmer agrupa os avisos de terceiros em uma única linha:

warning IL3053: Assembly 'FluentValidation' produced AOT analysis warnings.

Só com -p:TrimmerSingleWarn=false você vê o IL3050 real apontando para EnumValidator.cs(103) e Enum.GetValues(Type).

A correção de uma linha

O PR #2371 substitui a chamada por Enum.GetValuesAsUnderlyingType(enumType), disponível desde o .NET 8 (que é o target mínimo do FluentValidation 12). Ele retorna os valores em box como o tipo inteiro subjacente, então nenhum array tipado como enum é criado, e o loop existente com Convert.ToInt64 continua funcionando sem alterações. Nenhuma mudança de API, nenhuma mudança de comportamento, e ainda elimina uma ida e volta de box/unbox por valor.

// FluentValidation 12.2.0, EnumValidator.EvaluateFlagEnumValues
foreach (var enumValue in Enum.GetValuesAsUnderlyingType(enumType)) {
    var enumValueAsInt64 = Convert.ToInt64(enumValue);
    // ...
}

Com o 12.2.0, o aviso IL3053 do FluentValidation some completamente da saída da publicação neste exemplo.

A outra correção que vale conhecer

O 12.2.0 também corrige context.PropertyPath dentro de um .When() em RuleForEach (#2363). A condição via o caminho do item anterior (ou null para o primeiro), porque o contexto era atualizado depois que a condição rodava. Se você monta chaves de erro ou linhas de log a partir de PropertyPath dentro de condições, estava recebendo caminhos deslocados em um.

O que fazer

Atualize o pacote:

dotnet add package FluentValidation --version 12.2.0

Se você distribui com native AOT, defina também TrimmerSingleWarn como false no CI pelo menos uma vez. Linhas IL3053 agrupadas são o caminho pelo qual bugs como este chegam à produção. E se você ainda está decidindo se precisa do FluentValidation no ASP.NET Core 11, eu o comparei com a opção nativa em validação em minimal APIs vs FluentValidation.

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Voltar