Objetivo: validar CNPJ sem libs externas, formatar/máscara e integrar com DataAnnotations/ASP.NET.
Regras da validação (resumo)
Limpe caracteres não numéricos.
Tem que ter 14 dígitos.
Sequências repetidas (ex.:
000...
) são inválidas.Calcule os 2 dígitos verificadores com módulo 11:
1º DV (12 dígitos) pesos:
5 4 3 2 9 8 7 6 5 4 3 2
2º DV (12 + DV1) pesos:
6 5 4 3 2 9 8 7 6 5 4 3 2
Regra:r = soma % 11
; ser < 2
→ DV =0
, senão11 - r
.
Complexidade: O(14) — leve para qualquer request.
Implementação pronta (C#)
Crie um arquivo Cnpj.cs
:
using System.Linq;
using System.Text.RegularExpressions;
public static class Cnpj
{
public static bool IsValid(string? input)
{
var s = OnlyDigits(input);
if (s.Length != 14) return false;
if (s.Distinct().Count() == 1) return false; // todos iguais
int[] w1 = {5,4,3,2,9,8,7,6,5,4,3,2};
int[] w2 = {6,5,4,3,2,9,8,7,6,5,4,3,2};
int Sum(string digits, int[] weights)
=> digits.Select((ch, i) => (ch - '0') * weights[i]).Sum();
var base12 = s[..12];
var r1 = Sum(base12, w1) % 11;
var dv1 = r1 < 2 ? 0 : 11 - r1;
var r2 = (Sum(base12, w2[..12]) + dv1 * w2[12]) % 11;
var dv2 = r2 < 2 ? 0 : 11 - r2;
return s[12] - '0' == dv1 && s[13] - '0' == dv2;
}
public static string OnlyDigits(string? input)
=> Regex.Replace(input ?? string.Empty, @"\D", "");
// NN.NNN.NNN/NNNN-NN
public static string Format(string? input)
{
var s = OnlyDigits(input);
if (s.Length != 14) return input ?? string.Empty;
return $"{s[..2]}.{s.Substring(2,3)}.{s.Substring(5,3)}/{s.Substring(8,4)}-{s.Substring(12,2)}";
}
}
DataAnnotations: atributo [Cnpj]
Para validar DTOs/ViewModels automaticamente:
using System.ComponentModel.DataAnnotations;
public class CnpjAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext context)
{
if (value is null) return ValidationResult.Success; // use [Required] para obrigatoriedade
var s = value as string ?? value.ToString()!;
return Cnpj.IsValid(s)
? ValidationResult.Success
: new ValidationResult(ErrorMessage ?? "CNPJ inválido.");
}
}
Uso em um DTO:
using System.ComponentModel.DataAnnotations;
public record EmpresaDto(
[Required, Cnpj] string Cnpj,
[Required, StringLength(60)] string RazaoSocial
);
ASP.NET Core (Minimal API) — exemplo de integração
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/empresas", (EmpresaDto dto) =>
{
// ModelState é validado automaticamente ao bind do record com DataAnnotations
var cnpjFormatado = Cnpj.Format(dto.Cnpj);
return Results.Ok(new { dto.RazaoSocial, Cnpj = cnpjFormatado });
});
app.Run();
Frontend envia com ou sem máscara; o servidor valida e devolve formatado.
Exemplos para testar
Válidos:
04.252.011/0001-10
11.444.777/0001-61
12.345.678/0001-95
Inválidos:
00.000.000/0000-00
(todos iguais)04.252.011/0001-11
(DV alterado)11.444.777/0001-6
(tamanho errado)
Testes (xUnit)
using Xunit;
public class CnpjTests
{
[Theory]
[InlineData("04.252.011/0001-10")]
[InlineData("11.444.777/0001-61")]
[InlineData("12.345.678/0001-95")]
[InlineData("04252011000110")] // sem máscara
public void Deve_validar_cnpjs_validos(string cnpj)
=> Assert.True(Cnpj.IsValid(cnpj));
[Theory]
[InlineData("00.000.000/0000-00")]
[InlineData("11.444.777/0001-60")]
[InlineData("11.444.777/0001-6")]
[InlineData("")]
[InlineData(null)]
public void Deve_rejeitar_cnpjs_invalidos(string cnpj)
=> Assert.False(Cnpj.IsValid(cnpj));
}