Classe estática em C# 12 / .NET 8 sem pacotes externos, mais um ValidationAttribute para usar nos modelos do ASP.NET Core e do Entity Framework.
0=0 … 9=9, A=17 … Z=42), os pesos vão de 2 a 9 da direita para a esquerda, soma-se tudo, divide-se por 11 e o dígito é 0 quando o resto é menor que 2, ou 11 menos o resto. Ver a conta detalhada.Código completo
using System;
using System.Text.RegularExpressions;
namespace Exemplo.Documentos;
public static partial class Cnpj
{
[GeneratedRegex("[^0-9A-Z]")]
private static partial Regex NaoAlfanumerico();
[GeneratedRegex(@"^[0-9A-Z]{12}\d{2}$")]
private static partial Regex Formato();
[GeneratedRegex(@"^(.)\1{13}$")]
private static partial Regex Repetido();
/// <summary>Remove máscara e normaliza para maiúsculas.</summary>
public static string Limpar(string? valor) =>
string.IsNullOrEmpty(valor)
? string.Empty
: NaoAlfanumerico().Replace(valor.ToUpperInvariant(), string.Empty);
/// <summary>Calcula um dígito do módulo 11 (valor do caractere = ASCII - 48).</summary>
private static int Digito(ReadOnlySpan<char> sequencia)
{
var soma = 0;
for (var i = 0; i < sequencia.Length; i++)
{
var valor = sequencia[i] - 48;
var peso = ((sequencia.Length - 1 - i) % 8) + 2;
soma += valor * peso;
}
var resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
}
/// <summary>Devolve os dois dígitos verificadores da base de 12 caracteres.</summary>
public static string CalcularDv(string baseCnpj)
{
var primeiro = Digito(baseCnpj);
var segundo = Digito($"{baseCnpj}{primeiro}");
return $"{primeiro}{segundo}";
}
/// <summary>Valida CNPJ numérico ou alfanumérico, com ou sem máscara.</summary>
public static bool Valido(string? valor)
{
var cnpj = Limpar(valor);
if (!Formato().IsMatch(cnpj) || Repetido().IsMatch(cnpj))
{
return false;
}
return CalcularDv(cnpj[..12]) == cnpj[12..];
}
/// <summary>Aplica a máscara 00.000.000/0000-00.</summary>
public static string Formatar(string valor)
{
var cnpj = Limpar(valor);
return cnpj.Length != 14
? cnpj
: $"{cnpj[..2]}.{cnpj[2..5]}.{cnpj[5..8]}/{cnpj[8..12]}-{cnpj[12..]}";
}
}
Testes e integração com o framework
// Atributo de validação
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class CnpjValidoAttribute : ValidationAttribute
{
public override bool IsValid(object? valor) =>
valor is null || (valor is string texto && Cnpj.Valido(texto));
public override string FormatErrorMessage(string nome) => $"O campo {nome} não é um CNPJ válido.";
}
public class EmpresaDto
{
[Required, StringLength(120)]
public string RazaoSocial { get; set; } = string.Empty;
[Required, CnpjValido]
public string Documento { get; set; } = string.Empty;
}
// Teste
public class CnpjTests
{
[Theory]
[InlineData("12.ABC.345/01DE-35", true)]
[InlineData("12abc34501de35", true)]
[InlineData("11.222.333/0001-81", true)]
[InlineData("11.222.333/0001-82", false)]
[InlineData("12ABC34501DEAB", false)]
public void ValidaCnpj(string entrada, bool esperado) =>
Assert.Equal(esperado, Cnpj.Valido(entrada));
[Fact]
public void CalculaDvDoExemploOficial() =>
Assert.Equal("35", Cnpj.CalcularDv("12ABC34501DE"));
}
Detalhes que costumam quebrar em C#
[GeneratedRegex](.NET 7+) compila a expressão em tempo de build. Em projetos mais antigos, troque pornew Regex(..., RegexOptions.Compiled)em campostatic readonly.- Se a coluna do Entity Framework estiver como
long, migre parastringcomHasMaxLength(14)antes de julho de 2026. - Use
ToUpperInvariant()e nãoToUpper(): cultura turca transformaiemİe quebra a normalização.
Casos de teste recomendados
Cubra pelo menos estes cenários — eles pegam praticamente todos os erros de implementação:
| Entrada | Esperado | O que testa |
|---|---|---|
12.ABC.345/01DE-35 | válido | exemplo oficial, com máscara |
12abc34501de35 | válido | normalização para maiúsculas |
11.222.333/0001-81 | válido | compatibilidade com o formato numérico |
11.222.333/0001-82 | inválido | dígito verificador errado |
12ABC34501DEAB | inválido | letra no dígito verificador |
12ABC34501DE3 | inválido | tamanho incorreto |
00.000.000/0000-00 | inválido | sequência repetida |
"" / null | inválido | entrada vazia sem exceção |
Precisa de mais massa de teste? O gerador produz até 500 CNPJs alfanuméricos válidos de uma vez, com opção de baixar em .txt.