⚡
VITORNMS
>Início>Projetos>Bancada>Notas>Blog>Contato
GitHubLinkedIn
status: construindo
>Início>Projetos>Bancada>Notas>Blog>Contato
status: construindo
VITORNMS logoVITORNMS

Transformando ideias em soluções inovadoras de software & hardware. Construindo o futuro, um commit de cada vez.

Navegação

>Início>Projetos>Bancada>Blog>Contato

Contato

vitornms@gmail.com+55 31 98415-2360Belo Horizonte, MG, Brasil
enviar um sinal→
Language
Forjado com& código

© 2026 VITORNMS — Todos os experimentos reservados

back to blog
backend

Padrões de API REST com ASP.NET Core C#

A transição brusca do Python/Django para o mundo tipado do C# e .NET 8. Um monorepo de 49 MB com Blazor, Capacitor, WebTorrent e SQL direto — e 89 commits depois, o que aprendi.

VN

Vitor Neuenschwander

CS Student & Developer

Sep 12, 202418 min
#csharp#dotnet#aspnet-core#rest-api#postgresql

De Python para C#: Choque de Tipagem

Depois de meses confortável com Django e Python, o projeto "Educação Inteligente" me jogou no mundo do .NET 8 e C# 12. A transição foi brusca: de um mundo interpretado e dinâmico (dict["chave"]) para tipagem estática, compilação e Dependency Injection em tudo.

O projeto nasceu dentro da organização pai-educacao-inteligente no GitHub. Repositório privado, criado em agosto de 2025, com 89 commits e 5 contribuidores. O resultado? Um monorepo de ~49 MB com 4 componentes convivendo juntos — e funcionando.


A Loucura do Monorepo: 4 Componentes, 1 Repositório

educacao-inteligente/          (~49 MB, 89 commits)
├── EducacaoInteligente/       # Blazor WebAssembly — Frontend SPA
│   └── .NET 8.0               # Renderizado no browser via WASM
├── EducacaoInteligenteAPI/     # ASP.NET Core Minimal API — Backend
│   └── .NET 8.0               # PostgreSQL + JWT + RBAC
├── AndroidApplication/        # Capacitor 7.4.4 — Wrapper Mobile
│   └── com.Transcolar.EducacaoInteligente
├── VideoSharing/              # WebTorrent 2.8.4 — P2P de Vídeos
│   └── bittorrent-tracker
├── Executavel/                # Builds publicados
└── docs/                      # Documentação
Composição por linguagem:
LinguagemBytes%
HTML276.33860.7%
C#112.04224.6%
CSS40.7459.0%
JavaScript24.5505.4%
Java1.3140.3%
Os 60% de HTML vêm do Blazor WebAssembly — cada componente .razor é basicamente HTML com C# embutido.

Minimal API: Menos Cerimônia, Mais Controle

O ASP.NET Core Minimal API elimina controllers, startup pesadas e convenções implícitas. Cada endpoint é definido diretamente no Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Injeção de dependências modular
builder.Services.AddScoped<NpgsqlConnection>(sp =>
    new NpgsqlConnection(
        builder.Configuration.GetConnectionString("Default")
    ));

var app = builder.Build();

// Endpoints organizados por módulo
app.MapAuthEndpoints();      // /auth
app.MapAlunoEndpoints();     // /alunos
app.MapProfessorEndpoints(); // /professores
app.MapGestorEndpoints();    // /gestores (dashboard admin)
app.MapConteudoEndpoints();  // /conteudos

app.Run();

Cada MapXxxEndpoints() é um extension method que agrupa as rotas. Sem controllers de 500 linhas, sem herança pesada. Cada grupo é independente e testável.


SQL Direto com Npgsql: Sem ORM

Entity Framework Core é fantástico para prototipação rápida. Mas neste projeto, queria controle total sobre as queries e as transações. O Npgsql 8.0.7 permite injetar SQL diretamente no PostgreSQL:

app.MapGet("/api/alunos", [Authorize(Roles = "Professor,Gestor")]
async (NpgsqlConnection db) =>
{
    await db.OpenAsync();

    await using var cmd = new NpgsqlCommand(
        "SELECT id, nome, turma FROM alunos ORDER BY nome", db
    );

    var reader = await cmd.ExecuteReaderAsync();
    var alunos = new List<object>();

    while (await reader.ReadAsync())
    {
        alunos.Add(new {
            Id = reader.GetInt64(0),
            Nome = reader.GetString(1),
            Turma = reader.GetString(2)
        });
    }

    return Results.Ok(alunos);
});

Sem overhead de ORM, sem geração de SQL implícita, sem surpresas de lazy loading, sem migrations automáticas. Cada query é intencional e otimizada.

Quando precisas saber exatamente que SQL está a ser executado e quando, Npgsql direto é imbatível.


JWT + RBAC: Três Perfis, Três Mundos

A gestão de perfis (Aluno, Professor, Gestor) usa JWT com Role-Based Access Control. Cada endpoint declara explicitamente quem pode acessar:

// Configuração completa do JWT
builder.Services.AddAuthentication(
    JwtBearerDefaults.AuthenticationScheme
).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new()
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(
                builder.Configuration["Jwt:Key"]!
            ))
    };
});

// Aplicação por endpoint:
// [Authorize(Roles = "Aluno")]     → Só estudantes
// [Authorize(Roles = "Professor")] → Só professores
// [Authorize(Roles = "Professor,Gestor")] → Ambos
Pacotes de autenticação:
  • Microsoft.AspNetCore.Authentication.JwtBearer 8.0.8
  • System.IdentityModel.Tokens.Jwt 8.0.2
  • Microsoft.IdentityModel.Tokens 8.0.2

DTOs: Porteiros Implacáveis

Data Transfer Objects agem como filtros rigorosos na fronteira da API. Definidos como records do C# (imutáveis por design), só o que está no DTO entra:

// Envio de resposta pelo aluno
public record RespostaEnvio(
    long IdAluno,
    long IdQuestao,
    string RespostaDada
);

// Associação de vídeo a conteúdo
public record AssociaVideo(
    long IdConteudo,
    long IdVideo
);

// Criação de conteúdo pelo professor
public record CriarConteudo(
    long IdDisciplina,
    string Nome,
    string? Descricao  // nullable!
);

// Criação de questão
public record CriarQuestao(
    long IdConteudo,
    string Enunciado,
    string Alternativas
);

// Credenciais de login
public record Credenciais(
    string Login,
    string Senha
);

Se mandares um campo extra no JSON? Ignorado. Tipo errado? Erro de compilação — não em runtime como Python, mas no build. Segurança by design.


Endpoint na Prática: Envio de Resposta

Juntando tudo — JWT, DTO, Npgsql — num endpoint real:

app.MapPost("/api/respostas", [Authorize(Roles = "Aluno")]
async (RespostaEnvio dto, NpgsqlConnection db) =>
{
    await using var cmd = new NpgsqlCommand(
        @"INSERT INTO respostas
          (id_aluno, id_questao, resposta, data_envio)
          VALUES (@aluno, @questao, @resp, NOW())", db);

    cmd.Parameters.AddWithValue("aluno", dto.IdAluno);
    cmd.Parameters.AddWithValue("questao", dto.IdQuestao);
    cmd.Parameters.AddWithValue("resp", dto.RespostaDada);

    await db.OpenAsync();
    await cmd.ExecuteNonQueryAsync();
    return Results.Created();
});

Decomposição:

  1. [Authorize(Roles = "Aluno")] → Só alunos autenticados com JWT válido
  2. RespostaEnvio dto → Deserialização automática + validação de tipos
  3. NpgsqlConnection db → Injetado via DI, sem new nem using manual
  4. SQL parametrizado → Zero risco de SQL injection

WebTorrent: P2P para Vídeos Educacionais

O módulo VideoSharing usa webtorrent 2.8.4 e bittorrent-tracker 11.2.1 para distribuição peer-to-peer de vídeos:

import WebTorrent from 'webtorrent'

const client = new WebTorrent()

// Cada aluno que assiste um vídeo se torna um "seed"
client.seed(videoFile, { announce: trackerUrl }, (torrent) => {
    console.log('Seeding:', torrent.magnetURI)
})

// Outros alunos baixam diretamente dos colegas
client.add(magnetURI, (torrent) => {
    torrent.files[0].renderTo('video#player')
})

O resultado: quanto mais alunos assistem, mais rápido o vídeo carrega para os próximos — inversão completa do modelo servidor-centrado.


Blazor WebAssembly: C# no Browser

O frontend é inteiramente Blazor WebAssembly — C# compilado para WASM e executado no browser. Sem JavaScript framework, sem npm, sem webpack. Componentes .razor com lógica em C#:

@page "/dashboard"
@attribute [Authorize(Roles = "Aluno")]

<h1>Dashboard do Aluno</h1>

@if (disciplinas is null)
{
    <p>Carregando...</p>
}
else
{
    @foreach (var disc in disciplinas)
    {
        <DisciplinaCard Disciplina="@disc" />
    }
}

@code {
    private List<Disciplina>? disciplinas;

    protected override async Task OnInitializedAsync()
    {
        disciplinas = await Http.GetFromJsonAsync<List<Disciplina>>(
            "/api/disciplinas"
        );
    }
}

E com o Capacitor 7.4.4, o mesmo frontend Blazor é empacotado como app nativo Android (com.Transcolar.EducacaoInteligente).


A Equipa

O projeto teve 5 contribuidores:

ContribuidorCommits
ArthurMacielSs49
neuxxkk (eu)19
lucasvra11
obrandemburg8
Vitorelena2

O que Levei Deste Projeto

A transição Python → C# foi dolorosa mas reveladora. Três conclusões:

  1. Tipagem estática não é burocracia — é documentação viva que o compilador valida. DTOs como records eliminam classes inteiras de bugs
  2. Minimal API prova que .NET pode ser leve — tão expressivo quanto Flask, com a performance de linguagem compilada
  3. SQL direto não é regressão — quando precisas de controle, Npgsql é mais honesto que qualquer ORM. Sabes exatamente o que vai para o banco
Contents
share
share: