Implementando o Back-end com GitHub Copilot: .NET Minimal API, DTOs e EF Core InMemory na prática (Parte 3)

Implementando o Back-end com GitHub Copilot: .NET Minimal API, DTOs e EF Core InMemory na prática (Parte 3)

28 de julho de 2026

Nas duas primeiras partes desta série, o foco foi inteiramente o front-end. Na Parte 1, construí a camada de governança do GitHub Copilot para um projeto Vue 3 com PrimeVue 4, com um arquivo de instructions, um agent especializado e uma skill de criação de componentes. Na Parte 2, integrei o Tailwind CSS aos tokens do PrimeVue mantendo essa mesma governança, eliminando mais de 400 linhas de CSS scoped duplicado. Ao final da Parte 2, os formulários de Sign in, Create account e Reset password estavam visualmente prontos, consistentes e sem erros de tipagem, mas ainda emitiam eventos que não chegavam a lugar nenhum. Nesta Parte 3, esse vazio é preenchido: entra em cena o back-end real do projeto, construído em .NET com apoio direto do GitHub Copilot.

→ Sign in

Create account

→ Reset password

Diferente do front-end, onde a governança vive em um arquivo de instructions carregado automaticamente para todo arquivo .vue, o back-end deste projeto não precisou de um arquivo de instructions próprio. A razão é simples: são três endpoints HTTP com um contrato já definido pelo front-end existente (api.ts e auth.service.ts). Em vez de documentar regras para dezenas de arquivos futuros, o briefing técnico foi passado diretamente ao GitHub Copilot em modo de conversa, descrevendo a stack esperada: uma API .NET usando Minimal APIs, DTOs fortemente tipados para request e response, e o Entity Framework Core com o provider InMemory como base de dados. Esse tipo de prompt funciona como um contrato: ao declarar a tecnologia e a forma esperada dos dados antes de pedir qualquer implementação, o GitHub Copilot para de "adivinhar" a arquitetura e passa a gerar código já alinhado ao que o front-end espera consumir.

Para uma prova de conceito de front-end como essa, cujo objetivo é validar chamadas HTTP reais de registro, login e recuperação de senha, o Minimal API do ASP.NET Core é a escolha natural desde o início. São três endpoints HTTP simples, sem filas, sem timers e sem bindings de infraestrutura, então o back-end foi desenhado para rodar direto com dotnet run sobre Kestrel, sem nenhuma camada de hosting adicional. O api.csproj reflete essa simplicidade:

Uma única dependência de pacote, o Microsoft.EntityFrameworkCore.InMemory, é suficiente para simular uma base de dados persistente sem exigir Docker, SQL Server ou Azurite rodando localmente. Isso é particularmente valioso para uma prova de conceito de front-end, onde o objetivo é validar chamadas HTTP reais, não montar infraestrutura de dados.

O ponto de entrada da aplicação já mostra o quanto o Minimal API reduz o boilerplate:

AddDbContext<AppDbContext>()
Registra o AppDbContext no container de injeção de dependência, configurado para usar o provider InMemory com o nome de base MockDb.

AddCors() e UseCors()
Configuram e ativam uma política de CORS liberando http://localhost:5173, exatamente a porta onde o Vite serve o front-end, permitindo que o navegador aceite as respostas da API.

MapAuthEndpoints()
Método de extensão que registra os três endpoints de autenticação no pipeline HTTP.

UseUrls("http://localhost:7071")
Fixa a porta do Kestrel na mesma que o vite.config.ts já usa como proxy para api, o que significa que o front-end não precisou de nenhuma alteração para passar a falar com o back-end.

O modelo de dados também é enxuto. User é uma entidade simples do EF Core, e AppDbContext define apenas o DbSet e uma constraint de unicidade no e-mail:

OnModelCreating()
Declara um índice único sobre Email, garantindo que o provider InMemory rejeite duplicatas da mesma forma que uma base relacional real rejeitaria.

Foi exatamente nesse ponto do desenvolvimento que as sugestões inline do GitHub Copilot (não o chat, o autocomplete "ghost text" direto no editor) tiveram o maior impacto no ritmo de implementação. Ao abrir o arquivo AppDbContext.cs e começar a digitar o método OnModelCreating, o próprio nome do método e a entidade User já aberta em outra aba deram contexto suficiente para o GitHub Copilot sugerir a constraint de unicidade completa como ghost text, aceita com um simples Tab. Esse é o uso mais básico e ainda assim mais valioso do GitHub Copilot em um back-end: contexto do arquivo, do nome do método e de arquivos vizinhos abertos já é o bastante para acelerar código repetitivo do EF Core sem sair do editor.

Os DTOs (Data Transfer Objects) definem exatamente o formato de request e response que o front-end espera, e aqui o benefício de "instruir por contrato" fica visível:

RegisterRequestLoginRequestForgotPasswordRequest
Representam exatamente o corpo JSON enviado pelo front-end em cada chamada, com propriedades anuláveis para permitir validação explícita em vez de depender do model binding para rejeitar payloads incompletos.

AuthUserResponse
É a forma pública e segura de devolver um usuário: nunca inclui o hash da senha, apenas IdName e Email.

MessageResponse
Um envelope único para mensagens de erro e sucesso, usado tanto no 400 Bad Request de validação quanto no 200 OK de esqueci minha senha.

Como o Minimal APIs do ASP.NET Core já usa JsonSerializerDefaults.Web por padrão (camelCase, case-insensitive), esses records são serializados exatamente no formato que auth.service.ts já espera consumir, sem nenhuma camada extra de mapeamento manual.

O hashing de senha foi outro ponto onde os comentários funcionaram como diretiva direta para o GitHub Copilot gerar código. Escrever o comentário de resumo da classe antes do corpo do método é suficiente para que o autocomplete gere a implementação de PBKDF2 completa usando Rfc2898DeriveBytes:

Hash()
Gera um salt aleatório, deriva o hash com PBKDF2 e serializa iterações, salt e hash em uma única string separada por pontos.

Verify()
Faz o parse do hash armazenado, deriva o hash da senha informada com o mesmo salt e número de iterações, e compara os dois usando CryptographicOperations.FixedTimeEquals.

Esse FixedTimeEquals é um bom exemplo de quando vale a pena abrir o painel de completions com Ctrl+Enter em vez de aceitar a primeira sugestão com Tab. Ao pedir alternativas para a comparação final entre o hash calculado e o hash armazenado, o painel mostra lado a lado diferentes implementações possíveis, e a comparação de tempo constante do CryptographicOperations.FixedTimeEquals se destaca das demais por evitar que a diferença de tempo de execução vaze informação sobre a senha, um cuidado que só faz sentido comparando as opções antes de aceitar qualquer uma delas.

Os três endpoints de autenticação concentram a maior parte da lógica de negócio, e aqui os comentários deixados no código são as próprias diretivas usadas para orientar o GitHub Copilot durante a escrita:

using System.Text.RegularExpressions;
using Api.Data;
using Api.Dtos;
using Api.Models;
using Api.Services;
using Microsoft.EntityFrameworkCore;

namespace Api.Endpoints;

public static class AuthEndpoints
{
    private static readonly Regex EmailPattern = new(@"^[^\s@]+@[^\s@]+\.[^\s@]+$", RegexOptions.Compiled);
    private const string InvalidCredentialsMessage = "Invalid email or password.";
    private const string ForgotPasswordSuccessMessage =
        "If an account exists for this email, you'll receive a reset link shortly.";

    public static void MapAuthEndpoints(this WebApplication app)
    {
        var group = app.MapGroup("/api");

        group.MapPost("/register", Register);
        group.MapPost("/login", Login);
        group.MapPost("/forgot-password", ForgotPassword);
    }

    private static async Task<IResult> Login(LoginRequest request, AppDbContext db)
    {
        var email = request.Email?.Trim() ?? "";
        var password = request.Password ?? "";

        var user = await db.Users.FirstOrDefaultAsync(u => u.Email.ToLower() == email.ToLower());

        // Same generic error for "no such user" and "wrong password" — avoids account enumeration.
        if (user is null || !PasswordHasher.Verify(password, user.PasswordHash))
        {
            return Results.Json(new MessageResponse(InvalidCredentialsMessage), statusCode: StatusCodes.Status401Unauthorized);
        }

        return Results.Ok(new AuthUserResponse(user.Id, user.Name, user.Email));
    }

    private static IResult ForgotPassword(ForgotPasswordRequest request)
    {
        var email = request.Email?.Trim() ?? "";

        if (string.IsNullOrWhiteSpace(email) || !EmailPattern.IsMatch(email))
        {
            return Results.BadRequest(new MessageResponse("Enter a valid email address."));
        }

        // Always return the same generic success message, regardless of whether the account
        // exists, to avoid leaking which emails are registered (OWASP A07).
        return Results.Ok(new MessageResponse(ForgotPasswordSuccessMessage));
    }
}

MapAuthEndpoints()
Cria um grupo de rotas sob api e registra os três handlers de RegisterLogin e ForgotPassword como MapPost.

Login()
Busca o usuário por e-mail e verifica a senha com PasswordHasher.Verify(), devolvendo sempre a mesma mensagem genérica de erro tanto para usuário inexistente quanto para senha incorreta.

ForgotPassword()
Valida apenas o formato do e-mail e sempre responde 200 OK com a mesma mensagem, independentemente de a conta existir ou não.

Esses dois comentários, "avoids account enumeration" e "OWASP A07", foram digitados primeiro, como diretiva, exatamente no padrão descrito na documentação de sugestões inline do GitHub Copilot no VS Code: um comentário fraseado como instrução direciona o autocomplete a gerar a implementação correspondente, em vez de apenas complementar o código já escrito. Declarar a intenção de segurança em uma frase antes de qualquer lógica foi suficiente para que a sugestão do GitHub Copilot para o handler de login já viesse com a mensagem genérica correta, cobrindo tanto o caso de usuário inexistente quanto o de senha incorreta com a mesma resposta, exatamente o tipo de cuidado que o OWASP Top 10 recomenda para evitar vazamento de informação em falhas de autenticação.

O mesmo padrão de comentário-como-diretiva se repete no handler de registro, que usa uma constante EmailPattern compilada para validar o formato do e-mail antes de tocar no banco, e retorna 409 Conflict quando o e-mail já existe, fazendo a checagem de duplicidade case-insensitive diretamente na query com Email.ToLower(). Nenhuma dessas decisões precisou de um arquivo de instructions dedicado ao back-end: o contexto acumulado dentro da própria conversa com o GitHub Copilot, reforçado pelos comentários deixados no código à medida que cada regra de negócio era implementada, foi suficiente para manter a consistência entre os três endpoints.

Com os quatro arquivos no lugar (Program.cs, o modelo User, os DTOs e os endpoints), o teste é direto: dotnet build compila sem erros, dotnet run sobe o Kestrel em segundos servindo http://localhost:7071, e uma bateria de chamadas via curl confirma os três fluxos completos, incluindo o registro com nome, e-mail e senha inválidos retornando 400, o e-mail duplicado retornando 409, e o login retornando 401 genérico tanto para usuário inexistente quanto para senha errada. O mais importante para o front-end é que nada em api.ts ou auth.service.ts precisou mudar: como o proxy do Vite já apontava para http://localhost:7071/api, e os DTOs replicam exatamente o shape JSON que o front-end sempre esperou, os formulários construídos e migrados nas Partes 1 e 2 passaram a se autenticar contra uma API real sem tocar em uma única linha de código Vue.

O que essa Parte 3 deixa mais claro é que a governança que o GitHub Copilot precisa para gerar código consistente muda de forma dependendo da natureza do projeto. No front-end, com dezenas de componentes Vue seguindo o mesmo padrão visual, um arquivo de instructions carregado automaticamente é o mecanismo certo. No back-end, com poucos arquivos e um contrato de API já definido, o mesmo resultado foi alcançado descrevendo a stack tecnológica diretamente na conversa e reforçando decisões de segurança pontuais com comentários que funcionam como diretivas para o autocomplete. Em ambos os casos, o ganho real não veio de "deixar a IA decidir", mas de dar ao GitHub Copilot o contexto certo, no lugar certo, no momento certo.


O código completo deste projeto está disponível no meu repositório no GitHub.

Links e Docs:

GitHub Copilot · Your AI pair programmer
GitHub Copilot works alongside you directly in your editor, suggesting whole lines or entire functions for you.
Minimal APIs quick reference
Provides an overview of Minimal APIs in ASP.NET Core
In-memory Database Provider - EF Core
Information on the Entity Framework Core in-memory database provider
Enable Cross-Origin Requests (CORS) in ASP.NET Core
Learn how CORS as a standard for allowing or rejecting cross-origin requests in an ASP.NET Core app.
Rfc2898DeriveBytes Class (System.Security.Cryptography)
Implements password-based key derivation functionality, PBKDF2, by using a pseudo-random number generator based on HMACSHA1.
Inline suggestions from GitHub Copilot in VS Code
Get AI-powered inline suggestions from GitHub Copilot in VS Code, including ghost text completions and next edit suggestions.
A07 Identification and Authentication Failures - OWASP Top 10:2021
OWASP Top 10:2021

Não esqueça de me seguir no LinkedIn para mais conteúdos.

Até a próxima!!!

Confira mais:

Fique por dentro das novidades

Assine nossa newsletter e receba as últimas atualizações e artigos diretamente em seu email.

Assinar gratuitamente