Na aula anterior, construímos uma API REST, mas com uma limitação séria: os dados viviam numa lista em memória que desaparecia a cada reinício do programa. Agora unimos as duas metades desta fase — a web da aula passada e a persistência das aulas de banco de dados — conectando a API a um banco real com o Entity Framework Core. E, ao fazê-lo, encontraremos um conceito que é um dos mais importantes do desenvolvimento profissional moderno, e que o ASP.NET Core torna natural: a injeção de dependência. Ela responde a uma pergunta que se insinuava: como as rotas da nossa API obtêm acesso ao banco de dados de forma organizada? A resposta — e o princípio de projeto por trás dela — vai muito além de APIs, e é uma marca do código bem estruturado. Esta aula une o que você já sabe e adiciona uma peça valiosa.
A pergunta: quem cria o acesso ao banco?
Nossas rotas precisarão de um contexto do EF Core (o LojaContext da Extensão #02) para conversar com o banco. A abordagem ingênua seria cada rota criar o seu próprio:
// Abordagem ingênua e problemática: cada rota cria seu próprio contexto.
app.MapGet("/produtos", async () =>
{
using var db = new LojaContext(); // a rota conhece e cria o acesso ao banco
return await db.Produtos.ToListAsync();
});
Isso funcionaria, mas traz problemas. A rota fica amarrada à criação concreta do contexto — se a forma de configurá-lo mudar (uma nova string de conexão, uma configuração diferente), você teria de alterar todas as rotas. Há duplicação — cada rota repete a criação. E testar a rota isoladamente fica difícil, pois ela sempre criará um banco real. O princípio violado é aquele que vimos na Extensão #03: dependa de abstrações, não de criações concretas espalhadas pelo código. A injeção de dependência resolve isso de forma elegante.
A ideia da injeção de dependência
A injeção de dependência inverte quem cria o quê. Em vez de cada parte do programa criar as coisas de que precisa, ela as recebe prontas, de fora, de um "provedor central" que sabe construí-las. Você registra, num único lugar, como cada coisa é criada; e, onde precisar dela, apenas a declara como necessária — e o sistema a entrega. A analogia: em vez de cada funcionário de uma oficina fabricar sua própria ferramenta, existe um almoxarifado central que entrega a ferramenta certa a quem a pede. Isso centraliza a configuração (muda-se num lugar só), elimina a duplicação, e facilita testes (pode-se entregar uma versão de teste da dependência). O ASP.NET Core tem esse "almoxarifado" embutido, e usá-lo é natural.
Passo 1: registrar o acesso ao banco
Primeiro, preparamos o projeto: adicionamos as bibliotecas do EF Core (como na Extensão #02) e registramos o contexto no "almoxarifado central". No Program.cs, antes do builder.Build():
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// REGISTRA o LojaContext no sistema de injeção, dizendo COMO criá-lo.
// Agora o ASP.NET Core sabe fabricar um LojaContext quando alguém precisar.
builder.Services.AddDbContext<LojaContext>(opcoes =>
opcoes.UseNpgsql("Host=localhost;Database=loja_db;Username=curso;Password=senha123"));
var app = builder.Build();
O builder.Services.AddDbContext<LojaContext>(...) é o registro: ele ensina o sistema a criar um LojaContext já configurado com a string de conexão. A configuração da conexão agora vive num único lugar — não mais espalhada pelas rotas. (Com esse registro, o LojaContext recebe a configuração de fora, então não precisa mais do método OnConfiguring que tinha na Extensão #02 — um detalhe que o registro cuida.)
Passo 2: receber o acesso nas rotas por injeção
Agora a parte elegante: as rotas simplesmente declaram que precisam de um LojaContext, como um parâmetro, e o ASP.NET Core o injeta automaticamente — cria um, entrega à rota, e o libera ao final. As rotas não criam nada; apenas recebem o que pedem:
// A rota DECLARA que precisa de um LojaContext (o parâmetro 'db'); o sistema o INJETA.
app.MapGet("/produtos", async (LojaContext db) =>
await db.Produtos.ToListAsync()); // consulta o banco (LINQ + assincronia)
app.MapGet("/produtos/{id}", async (int id, LojaContext db) =>
{
var produto = await db.Produtos.FindAsync(id);
return produto != null ? Results.Ok(produto) : Results.NotFound();
});
app.MapPost("/produtos", async (Produto novo, LojaContext db) =>
{
db.Produtos.Add(novo);
await db.SaveChangesAsync(); // persiste no banco (fase de concorrência)
return Results.Created($"/produtos/{novo.Id}", novo);
});
app.MapDelete("/produtos/{id}", async (int id, LojaContext db) =>
{
var produto = await db.Produtos.FindAsync(id);
if (produto == null) return Results.NotFound();
db.Produtos.Remove(produto);
await db.SaveChangesAsync();
return Results.NoContent();
});
app.Run();
Observe o parâmetro LojaContext db em cada rota. Você não escreve new LojaContext() em lugar nenhum — o ASP.NET Core vê que a rota precisa de um, consulta o registro (onde você o configurou), cria uma instância pronta, injeta-a, e a libera depois. A rota fica limpa, focada só na sua lógica, e a criação do acesso ao banco é responsabilidade do sistema. Compare com a versão ingênua do início: sumiu o using var db = new..., e com ele a amarração e a duplicação. E note como tudo se conecta: o LINQ e o SaveChangesAsync da fase de bancos, a assincronia (async/await) da fase de concorrência, os códigos de resposta e o tratamento de nulo — todos reaparecem, agora numa API que persiste de verdade.
A injeção de dependência além das APIs
Encerro com a perspectiva maior, pois é o que torna este conceito valioso. A injeção de dependência não é um truque do ASP.NET Core; é um princípio de projeto fundamental, usado em praticamente todo sistema profissional. Em qualquer programa não trivial, as partes dependem umas das outras — um serviço depende de um acesso a dados, que depende de uma configuração. Fazer cada parte criar suas próprias dependências gera um emaranhado rígido, difícil de mudar e de testar. A injeção de dependência desata esse nó: cada parte declara o que precisa, e um sistema central monta tudo. Isso concretiza a "dependência de abstrações" que vimos na Extensão #03 e a testabilidade da fase de testes (você pode injetar uma versão falsa de uma dependência nos testes). É por isso que a injeção de dependência é onipresente no C# profissional e além — e agora você não só sabe usá-la, como entende por que ela existe.
A API deixou de guardar dados em memória e passou a conversar com um banco de verdade. E, no caminho, apareceu um conceito que estrutura aplicações profissionais inteiras: em vez de cada parte do código criar por conta própria aquilo de que precisa, as dependências são registradas num lugar central e entregues a quem as declara.
A injeção de dependência resolve de uma vez o controle do tempo de vida dos recursos, a facilidade de substituir uma implementação por outra e a testabilidade do código. Ela não é um recurso de APIs nem do ASP.NET Core — é um padrão de projeto de aplicação, e reconhecê-lo aqui ajuda a identificá-lo em praticamente qualquer sistema bem estruturado.
Fontes e leituras recomendadas
- Injeção de dependência no ASP.NET Core — a explicação oficial do sistema de injeção; a base desta aula.
- EF Core com injeção de dependência — como registrar e injetar o contexto do banco.
- Injeção em APIs mínimas — como os parâmetros de rota recebem os serviços injetados.
- O princípio de inversão de dependência — o fundamento de projeto que a injeção concretiza.
- Tempos de vida de serviços — quanto tempo cada dependência injetada vive, para aprofundar.
Exercícios
Exercício 1
Pegue a API da aula anterior, adicione as bibliotecas do EF Core, registre o LojaContext com AddDbContext e a string de conexão do seu PostgreSQL, e aplique as migrações. Confirme que o banco está pronto para a API.
Ver resposta
✓ Resposta: Após adicionar as bibliotecas do EF Core e do Npgsql, registra-se no Program.cs: builder.Services.AddDbContext<LojaContext>(o => o.UseNpgsql("Host=localhost;Database=loja_db;Username=curso;Password=senha123"));. Com o LojaContext e a classe Produto definidos, dotnet ef migrations add ApiInicial e dotnet ef database update criam a tabela. O banco fica pronto para a API usar.
Exercício 2
Converta as quatro rotas para receberem o LojaContext por injeção (o parâmetro LojaContext db) e persistirem no banco com ToListAsync, FindAsync e SaveChangesAsync. Teste criando um produto via POST e confirmando, após reiniciar a API, que ele ainda está lá (persistência real).
Ver resposta
✓ Resposta: Cada rota passa a declarar LojaContext db como parâmetro e a usar o banco: o GET da lista com await db.Produtos.ToListAsync(), o GET por id com FindAsync, o POST com Add + SaveChangesAsync, o DELETE com Remove + SaveChangesAsync. Criando um produto via POST e reiniciando a API, um GET /produtos seguinte ainda o mostra — confirmando que agora os dados vivem no banco, não em memória volátil, e persistem entre execuções.
Exercício 3
Compare a rota MapGet("/produtos", ...) na versão ingênua (que fazia new LojaContext()) com a versão por injeção. Liste dois problemas concretos que a injeção de dependência resolve.
Ver resposta
✓ Resposta: A versão injetada resolve, entre outros: (a) a amarração à criação concreta — na versão ingênua, cada rota chamava new LojaContext() e conhecia a forma de construí-lo; se a configuração (como a string de conexão) mudasse, todas as rotas teriam de mudar. Com injeção, a configuração vive só no registro, num único lugar, e alterá-la afeta um ponto só. (b) A duplicação — a versão ingênua repetia a criação do contexto em cada rota; com injeção, a rota apenas declara que precisa de um contexto, sem repetir a criação, deixando o código mais limpo e a responsabilidade de criar centralizada no sistema.
Exercício 4
Explique, com suas palavras e a analogia do almoxarifado, o que é a injeção de dependência. Quem cria as dependências na abordagem ingênua, e quem as cria com injeção?
Ver resposta
✓ Resposta: A injeção de dependência é um mecanismo em que as partes de um programa, em vez de criarem as coisas de que precisam, as recebem prontas de um "provedor central". Na analogia do almoxarifado: em vez de cada funcionário fabricar sua própria ferramenta, existe um almoxarifado central que entrega a ferramenta certa a quem a pede — o funcionário só precisa dizer o que precisa. Na abordagem ingênua, cada parte (cada rota) cria suas próprias dependências (cada funcionário fabrica sua ferramenta), gerando duplicação e amarração. Com injeção, o sistema central (o almoxarifado, o ASP.NET Core) cria as dependências e as entrega a quem as declara necessárias (o almoxarifado fornece a ferramenta pronta), e a parte apenas recebe o que pediu.
Exercício 5
Sem código: a aula afirma que a injeção de dependência facilita os testes. Com base no que você aprendeu na fase de testes, explique por que receber uma dependência de fora (em vez de criá-la internamente) torna uma rota ou um método mais fácil de testar.
Ver resposta
✓ Resposta: Receber uma dependência de fora torna o código mais fácil de testar porque, como a rota ou o método não cria a dependência internamente, é possível, num teste, entregar-lhe uma versão controlada dessa dependência em vez da real. Por exemplo, em vez de a rota sempre usar o banco de dados real (lento e que exigiria um banco disponível), o teste poderia fornecer um acesso a dados falso ou em memória, preparado para o teste. Isso permite verificar a lógica da rota isoladamente, de forma rápida e previsível, sem depender de um banco real no ar. Se a rota criasse o próprio contexto internamente (a abordagem ingênua), ela sempre usaria o banco real, e não haveria como substituí-lo por uma versão de teste — tornando o teste lento, frágil e dependente de infraestrutura externa. Receber a dependência de fora dá ao teste o controle necessário para isolar o que está sendo verificado.