Como validar um CNPJ em C#

Objetivo: validar CNPJ sem libs externas, formatar/máscara e integrar com DataAnnotations/ASP.NET.

Regras da validação (resumo)

  1. Limpe caracteres não numéricos.

  2. Tem que ter 14 dígitos.

  3. Sequências repetidas (ex.: 000...) são inválidas.

  4. 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; se r < 2 → DV = 0, senão 11 - 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));
}