Validar, gerar e formatar CNPJ em C#
Classe estática em C# (.NET 8) para validar, gerar e formatar CNPJ numérico e alfanumérico, testes com xUnit [Theory] e chamada à API com HttpClient.
Em aplicações .NET, o CNPJ costuma aparecer como long em entidades antigas, como string com máscara em DTOs e, às vezes, como os dois no mesmo sistema. O CNPJ alfanumérico obriga a padronizar: o identificador passa a ter letras nos 12 primeiros caracteres e só pode ser texto. A classe abaixo trata os dois formatos com a mesma conta, em que cada caractere vale o código ASCII menos 48 (dígitos de 0 a 9, letras de 17 a 42), e compila em .NET 8 com nullable habilitado e os usings implícitos do SDK. Os demais impactos estão no guia CNPJ alfanumérico: o que muda no seu sistema.
Validar CNPJ em C# (numérico e alfanumérico)
using System.Security.Cryptography;
using System.Text.RegularExpressions;
namespace CpfDev;
public static class Cnpj
{
private static readonly int[] Pesos1 = { 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2 };
private static readonly int[] Pesos2 = { 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2 };
private static readonly Regex Separadores = new(@"[./\-\s]", RegexOptions.Compiled);
private static readonly Regex Formato = new(@"^[A-Z0-9]{12}[0-9]{2}\z", RegexOptions.Compiled);
private static int CalcularDigito(string doze, int[] pesos)
{
var soma = 0;
for (var i = 0; i < doze.Length; i++)
{
soma += (doze[i] - 48) * pesos[i];
}
var resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
}
public static bool Validar(string? valor)
{
if (valor is null) return false;
var cnpj = Separadores.Replace(valor.ToUpperInvariant(), "");
if (!Formato.IsMatch(cnpj)) return false;
var doze = cnpj[..12];
if (doze.All(c => c == doze[0])) return false;
var dv1 = CalcularDigito(doze, Pesos1);
var dv2 = CalcularDigito(doze + dv1, Pesos2);
return cnpj[12..] == $"{dv1}{dv2}";
}Detalhes da implementação:
ToUpperInvariantnão depende da cultura da thread, o que evita surpresas em servidores configurados em outro idioma.[0-9]em vez de\d. No .NET,\dcasa dígitos Unicode de outros alfabetos, que passariam pela regex e estragariam a conta.\zancora no fim real da string;$aceitaria uma quebra de linha final.doze.All(c => c == doze[0])recusa bases repetidas como 00.000.000/0000-00, que fecham a conta, mas não são CNPJs válidos.doze[i] - 48converte ocharpelo código:'0'vira 0 e'A'vira 17.char.GetNumericValuenão serve, porque devolve -1 para letras.
Com 11.222.333/0001-81, a primeira soma é 102 (resto 3, dígito 8) e a segunda é 120 (resto 10, dígito 1). Com 12.ABC.345/01DE-35, as somas são 459 e 424, os restos 8 e 6, e os dígitos 3 e 5. No FluentValidation, a regra fica RuleFor(x => x.Cnpj).Must(Cnpj.Validar).WithMessage("CNPJ inválido").
Gerar CNPJ válido em C#
Os métodos a seguir ficam dentro da mesma classe. RandomNumberGenerator.GetString (.NET 8) sorteia cada caractere com o gerador criptográfico do sistema.
private const string Digitos = "0123456789";
private const string Alfanumericos = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static string Gerar(bool alfanumerico = false, bool formatado = false)
{
var alfabeto = alfanumerico ? Alfanumericos : Digitos;
string doze;
do
{
doze = RandomNumberGenerator.GetString(alfabeto, 12);
} while (doze.All(c => c == doze[0]));
var dv1 = CalcularDigito(doze, Pesos1);
var dv2 = CalcularDigito(doze + dv1, Pesos2);
var cnpj = $"{doze}{dv1}{dv2}";
return formatado ? Formatar(cnpj) : cnpj;
}O CNPJ gerado passa na validação, mas pode coincidir com um CNPJ real por acaso; use-o em seeds e testes, nunca como dado de uma empresa. Para simular matriz e filiais, mantenha a raiz e varie a ordem (0001 é a matriz), como explica o guia da estrutura do CNPJ.
Formatar e limpar CNPJ em C#
public static string Limpar(string? valor) =>
Regex.Replace((valor ?? "").ToUpperInvariant(), "[^A-Z0-9]", "");
public static string Formatar(string? valor)
{
var c = Limpar(valor);
if (c.Length != 14)
throw new ArgumentException("CNPJ precisa de 14 caracteres", nameof(valor));
return $"{c[..2]}.{c[2..5]}.{c[5..8]}/{c[8..12]}-{c[12..]}";
}
}Cnpj.Formatar("12abc34501de35") devolve "12.ABC.345/01DE-35", e aplicar o método de novo no resultado não muda nada. No Entity Framework, mapeie a propriedade como string com HasMaxLength(14) e grave a versão limpa; uma coluna bigint não comporta letras. Para validar a máscara com uma única expressão, veja o guia de regex de CNPJ.
Testes
No xUnit, [Theory] com um [InlineData] por caso gera um teste para cada linha.
using CpfDev;
using Xunit;
public class CnpjTests
{
[Theory]
[InlineData("11.222.333/0001-81", true)]
[InlineData("12.ABC.345/01DE-35", true)]
[InlineData("11.222.333/0001-82", false)]
[InlineData("00.000.000/0000-00", false)]
public void Validar_DevolveOResultadoEsperado(string cnpj, bool esperado)
{
Assert.Equal(esperado, Cnpj.Validar(cnpj));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Gerar_ProduzCnpjsValidos(bool alfanumerico)
{
for (var i = 0; i < 1000; i++)
Assert.True(Cnpj.Validar(Cnpj.Gerar(alfanumerico)));
}
[Fact]
public void Formatar_EhIdempotente() =>
Assert.Equal("12.ABC.345/01DE-35", Cnpj.Formatar(Cnpj.Formatar("12abc34501de35")));
}Rode com dotnet test. Mais combinações estão nos exemplos de CNPJ alfanumérico para teste.
Usar a API em vez de reimplementar
A API do cpf.dev.br valida e gera CNPJs por HTTP, sem chave, com limite de 60 requisições por minuto por IP. Envie o CNPJ sem máscara, ou passe-o por Uri.EscapeDataString, que troca a barra por %2F. GetFromJsonAsync desserializa a resposta direto para um record.
using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://www.cpf.dev.br/") };
var r = await http.GetFromJsonAsync<RespostaCnpj>("api/v1/cnpj/validar/12ABC34501DE35");
Console.WriteLine(r);
// RespostaCnpj { Valido = True, Cnpj = 12ABC34501DE35, Formatado = 12.ABC.345/01DE-35,
// Formato = alfanumerico, Raiz = 12ABC345, Ordem = 01DE, Matriz = False }
var lote = await http.GetFromJsonAsync<RespostaLote>(
"api/v1/cnpj/gerar?quantidade=5&formato=alfanumerico&formatado=true");
Console.WriteLine(string.Join(", ", lote!.Cnpjs));
public record RespostaCnpj(bool Valido, string Cnpj, string Formatado, string Formato, string Raiz, string Ordem, bool Matriz);
public record RespostaLote(string[] Cnpjs);O exemplo cria um HttpClient direto para ficar curto; em ASP.NET Core, registre-o com IHttpClientFactory. Para formulários, a classe local continua sendo a melhor opção. Para conferir um número à mão, use o validador de CNPJ.