Transparência: sou fundador da CPFHub, citada no final como uma das opções. A validação mostrada aqui não depende de nenhum fornecedor.
Em projetos .NET brasileiros, CPF aparece em cadastro, emissão de nota, integração com ERP e onboarding. Este guia cobre: uma função de validação sem alocações desnecessárias, a regra no FluentValidation e uma consulta HTTP com IHttpClientFactory e Polly.
1. Validação com ReadOnlySpan<char>
public static class Cpf
{
public static string Normalizar(string? s) =>
new string((s ?? "").Where(char.IsAsciiDigit).ToArray());
public static bool Valido(string? entrada)
{
var cpf = Normalizar(entrada).AsSpan();
if (cpf.Length != 11) return false;
bool todosIguais = true;
for (int i = 1; i < 11; i++) if (cpf[i] != cpf[0]) { todosIguais = false; break; }
if (todosIguais) return false;
for (int t = 9; t < 11; t++)
{
int soma = 0;
for (int i = 0; i < t; i++) soma += (cpf[i] - '0') * (t + 1 - i);
int dv = soma * 10 % 11 % 10;
if (cpf[t] - '0' != dv) return false;
}
return true;
}
}
Use char.IsAsciiDigit (NET 7+) em vez de char.IsDigit: o segundo aceita dígitos de outros alfabetos, como os arábico-índicos, que não fazem sentido aqui.
2. FluentValidation
public class CadastroValidator : AbstractValidator<CadastroDto>
{
public CadastroValidator()
{
RuleFor(x => x.Nome).NotEmpty().MaximumLength(120);
RuleFor(x => x.Cpf)
.NotEmpty()
.Must(Cpf.Valido).WithMessage("CPF inválido");
}
}
Se você usa Data Annotations, crie um ValidationAttribute chamando o mesmo Cpf.Valido. O importante é ter uma implementação, testada, usada em todo lugar.
3. Persistência
No EF Core, guarde só dígitos e garanta unicidade:
modelBuilder.Entity<Cliente>()
.Property(c => c.Cpf).HasMaxLength(11).IsFixedLength();
modelBuilder.Entity<Cliente>()
.HasIndex(c => c.Cpf).IsUnique();
char(11) comunica a intenção e evita que alguém grave com máscara.
4. HttpClient tipado
O DV garante formato. Para saber se o CPF existe e obter nome e data de nascimento, você precisa de uma API. Registre um client tipado:
builder.Services.AddHttpClient<CpfClient>(c =>
{
c.BaseAddress = new Uri("https://api.cpfhub.io/");
c.Timeout = TimeSpan.FromSeconds(10);
c.DefaultRequestHeaders.Add("x-api-key", builder.Configuration["CpfHub:ApiKey"]);
});
public record CpfData(string Cpf, string Name, string Gender, string BirthDate);
public record CpfResponse(bool Success, CpfData? Data);
public class CpfClient(HttpClient http)
{
public async Task<CpfData?> ConsultarAsync(string cpf, CancellationToken ct = default)
{
var limpo = Cpf.Normalizar(cpf);
if (!Cpf.Valido(limpo)) throw new ArgumentException("CPF inválido");
using var res = await http.GetAsync($"cpf/{limpo}", ct);
switch ((int)res.StatusCode)
{
case 200:
var body = await res.Content.ReadFromJsonAsync<CpfResponse>(
new JsonSerializerOptions(JsonSerializerDefaults.Web), ct);
return body?.Data;
case 404:
return null; // não encontrado
case 429:
var retry = res.Headers.RetryAfter?.Delta;
throw new LimiteException(retry);
default:
res.EnsureSuccessStatusCode();
return null;
}
}
}
JsonSerializerDefaults.Web faz o binding case-insensitive (name → Name).
5. Polly: retry só no que faz sentido
Com Microsoft.Extensions.Http.Resilience (.NET 8):
builder.Services.AddHttpClient<CpfClient>(...)
.AddStandardResilienceHandler(o =>
{
o.Retry.MaxRetryAttempts = 2;
o.Retry.ShouldHandle = args => ValueTask.FromResult(
args.Outcome.Result?.StatusCode is HttpStatusCode.BadGateway
or HttpStatusCode.ServiceUnavailable);
});
Não repita 401, 403 e 404: o resultado não muda. Para 429, respeite o Retry-After em vez de tentar de novo na hora.
6. Não guarde mais do que precisa
A LGPD pede minimização. Se você só precisa confirmar que o nome bate, guarde o resultado da verificação (ok/divergente e data), não necessariamente todos os campos retornados.
7. Testes
[Theory]
[InlineData("529.982.247-25", true)]
[InlineData("11111111111", false)]
[InlineData("52998224724", false)]
public void Valida(string cpf, bool esperado) => Assert.Equal(esperado, Cpf.Valido(cpf));
Para o CpfClient, injete um HttpMessageHandler falso que devolve 200, 404 e 429.
Sobre a CPFHub
A CPFHub é uma das APIs que você pode plugar no CpfClient: GET /cpf/{cpf} com x-api-key, ~150 ms de tempo típico (não SLA), 404 sem consumo de crédito e 50 créditos grátis por mês. Veja a documentação.
Top comments (0)