Les dues lliçons anteriors van donar a BiblioTech una interfície d'escriptori, primer amb Windows
Forms i després amb WPF —totes dues exclusives de Windows i pensades per a un únic usuari
executant l'aplicació al seu propi ordinador—. ASP.NET Core canvia radicalment aquest model:
en lloc d'una finestra, exposa la lògica de BiblioTech com un servei web, accessible per HTTP
des de qualsevol client —un navegador, una aplicació mòbil, un altre servei, o el mateix
HttpClient que ja vas fer servir al Mòdul 5 per consumir una API externa—. Aquesta lliçó
presenta ASP.NET Core centrant-se en les Minimal APIs, l'estil més modern i directe de
definir endpoints HTTP a .NET, i construeix una API real de BiblioTech amb operacions per
consultar el catàleg i registrar préstecs.
Contingut
- Què és ASP.NET Core i quin problema resol
- Minimal APIs davant Controllers
- Crear un projecte amb
dotnet new webapi - Enrutament i verbs HTTP:
app.MapGet/app.MapPost - Injecció de dependències bàsica al contenidor de serveis
- Retornar JSON: retomant
System.Text.Jsondel Mòdul 5 - Exemple complet: API mínima de BiblioTech amb
BibliotecaDbContext - Provar l'API
- Què és ASP.NET Core i quin problema resol
ASP.NET Core és el framework de .NET per construir aplicacions i serveis web: des d'APIs que només retornen dades (el focus d'aquesta lliçó) fins a llocs web complets amb pàgines HTML renderitzades al servidor. La seva peça central és el servidor web integrat (Kestrel), que escolta peticions HTTP entrants i les envia al codi C# que les atén:
flowchart LR
C["Client HTTP<br/>(navegador, app mobil, HttpClient...)"] -->|"GET /libros"| K["Kestrel<br/>(servidor web d'ASP.NET Core)"]
K --> E["Endpoint C#<br/>app.MapGet(...)"]
E --> B["Biblioteca<br/>(domini ja existent)"]
E -->|JSON| K
K -->|"Resposta HTTP"| C
A diferència de Windows Forms i WPF, una aplicació ASP.NET Core no té finestra ni interfície visual pròpia: s'executa com un procés en segon pla (en un servidor, en un contenidor, o localment durant el desenvolupament) i respon a peticions. Això la fa multiplataforma —s'executa igual a Windows, Linux o macOS— i adequada precisament per al paper que necessitava BiblioTech: un punt central al qual diferents clients (una futura app Blazor, una app MAUI, un tercer) puguin demanar dades del catàleg o registrar préstecs, sense duplicar la lògica de domini en cadascun d'ells.
- Minimal APIs davant Controllers
ASP.NET Core ofereix dos estils per definir endpoints HTTP:
| Minimal APIs | Controllers | |
|---|---|---|
| Com es defineixen els endpoints | Funcions lambda o mètodes registrats directament sobre app |
Classes Controller amb mètodes decorats amb atributs ([HttpGet], [HttpPost]) |
| Quantitat de codi per a un endpoint simple | Mínima: una línia per endpoint | Més gran: una classe completa, amb la seva pròpia estructura |
| Origen històric | Introduïdes a .NET 6 com a alternativa més lleugera | L'estil original d'ASP.NET Core (i d'ASP.NET MVC, anterior a .NET Core) |
| Quan convé | APIs petites o mitjanes, microserveis, prototips | APIs grans amb molts endpoints relacionats, que es beneficien de l'organització en classes |
Aquesta lliçó se centra en Minimal APIs per ser l'estil més simple i directe per començar, i perquè encaixa bé amb la mida de l'API de BiblioTech en aquesta etapa del curs. Els Controllers no desapareixen ni queden obsolets: per a una API amb desenes d'endpoints organitzats per recurs, l'estructura en classes dels Controllers pot resultar més còmoda de mantenir a llarg termini —una decisió de disseny, no una jerarquia d'opció "millor i pitjor".
- Crear un projecte amb
dotnet new webapi
dotnet new webapiEl fitxer central d'una Minimal API és Program.cs, on es configura i arrenca tot:
var builder = WebApplication.CreateBuilder(args);
// aqui es registren serveis al contenidor (apartat 5)
var app = builder.Build();
// aqui es defineixen els endpoints (apartat 4)
app.Run();WebApplication.CreateBuilder(args) prepara un builder amb la configuració per defecte (lectura
d'appsettings.json, sistema de logging, etc.); builder.Build() construeix l'aplicació (app)
a partir d'aquesta configuració; app.Run() arrenca Kestrel i es queda escoltant peticions fins
que el procés s'atura. Tot el codi que registra serveis va abans de Build(); tot el codi que
defineix endpoints va després.
- Enrutament i verbs HTTP:
app.MapGet/app.MapPost
app.MapGet/app.MapPostCada endpoint es registra indicant el verb HTTP, la ruta, i una funció que l'atén:
app.MapGet("/libros", () =>
{
return new List<string> { "Rayuela", "Ficciones" }; // simplificat, es completa a l'apartat 7
});
app.MapGet("/libros/{isbn}", (string isbn) =>
{
// {isbn} a la ruta es vincula automaticament al parametre "isbn" de la funcio
return $"Cercant el llibre amb ISBN {isbn}";
});
app.MapPost("/prestamos", (string isbn, int idSoci) =>
{
return $"Prestec registrat: ISBN {isbn}, soci {idSoci}";
});| Verb HTTP | Mètode d'ASP.NET Core | Ús típic |
|---|---|---|
GET |
app.MapGet(ruta, funcio) |
Consultar dades, sense modificar res al servidor |
POST |
app.MapPost(ruta, funcio) |
Crear alguna cosa nova (aquí, un préstec) |
PUT |
app.MapPut(ruta, funcio) |
Reemplaçar un recurs existent |
DELETE |
app.MapDelete(ruta, funcio) |
Eliminar un recurs |
{isbn} dins de la ruta és un paràmetre de ruta: ASP.NET Core extreu automàticament aquest
segment de la URL (/libros/978-84-376-0495-4) i el passa com a argument string isbn a la
funció, amb conversió de tipus automàtica si el paràmetre fos, per exemple, int. Aquest
mecanisme d'enllaç de paràmetres (binding) és anàleg, en esperit, al data binding de WPF:
tots dos eviten haver d'extreure i convertir dades manualment.
- Injecció de dependències bàsica al contenidor de serveis
ASP.NET Core porta integrat un contenidor d'injecció de dependències: un registre central de
serveis (builder.Services) del qual l'aplicació obté instàncies, en lloc que cada endpoint
construeixi les seves pròpies dependències amb new:
var builder = WebApplication.CreateBuilder(args);
// Registra Biblioteca com a servei unic compartit durant tota la vida de l'aplicacio
builder.Services.AddSingleton<Biblioteca>();
var app = builder.Build();
app.MapGet("/libros", (Biblioteca biblioteca) =>
{
// "biblioteca" es rep ja construida: ASP.NET Core la resol del contenidor automaticament
return biblioteca.Cataleg;
});AddSingleton<Biblioteca>() li diu al contenidor: "crea una única instància de Biblioteca i
reutilitza-la a cada petició que la necessiti". N'hi ha prou amb declarar Biblioteca biblioteca
com a paràmetre de la funció de l'endpoint perquè ASP.NET Core la resolgui i la injecti
automàticament, sense que l'endpoint hagi de saber com s'ha construït. Aquest mecanisme
—injecció de dependències— és un pilar central del disseny d'aplicacions .NET modernes, i
s'estudiarà en profunditat al Mòdul 8 (Bones Pràctiques i Patrons de Disseny); aquí n'hi ha prou
amb reconèixer el patró bàsic: registrar a builder.Services, rebre com a paràmetre a l'endpoint.
- Retornar JSON: retomant
System.Text.Json del Mòdul 5
System.Text.Json del Mòdul 5Quan un endpoint retorna un objecte o una col·lecció (com biblioteca.Cataleg a l'exemple
anterior), ASP.NET Core el serialitza automàticament a JSON fent servir System.Text.Json —la
mateixa llibreria ja usada al Mòdul 5 amb JsonSerializer.Serialize— sense que l'endpoint l'hagi
de cridar explícitament:
app.MapGet("/libros", (Biblioteca biblioteca) =>
{
return biblioteca.Cataleg; // ASP.NET Core serialitza aquesta llista a JSON automaticament
});[
{ "titol": "Rayuela", "autor": "Julio Cortazar", "disponible": true },
{ "titol": "Ficciones", "autor": "Jorge Luis Borges", "disponible": true }
]Per defecte, ASP.NET Core fa servir camelCase per a les claus del JSON de sortida (titol, no
Titol), la mateixa convenció que es va veure amb JsonNamingPolicy.CamelCase al Mòdul 5 —aquí
aplicada automàticament pel framework, sense configuració addicional—. Si l'endpoint necessita
control explícit sobre el codi d'estat HTTP de la resposta, pot retornar un IResult amb
Results.Ok(...), Results.NotFound() o Results.BadRequest(...) en lloc de retornar l'objecte
directament, com es veu a l'apartat 7.
- Exemple complet: API mínima de BiblioTech amb
BibliotecaDbContext
BibliotecaDbContextUnint les peces anteriors amb la persistència en base de dades ja construïda al Mòdul 5
(BibliotecaDbContext, amb Entity Framework Core), una API mínima realista per a BiblioTech:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<BibliotecaDbContext>(opcions =>
opcions.UseSqlite("Data Source=bibliotech.db"));
var app = builder.Build();
app.MapGet("/libros", async (BibliotecaDbContext context) =>
{
List<Llibre> llibres = await context.Llibres.ToListAsync(); // EF Core, Modul 5
return Results.Ok(llibres);
});
app.MapGet("/libros/{isbn}", async (string isbn, BibliotecaDbContext context) =>
{
Llibre? llibre = await context.Llibres.FirstOrDefaultAsync(l => l.Isbn == isbn);
if (llibre is null)
{
return Results.NotFound($"No existeix cap llibre amb ISBN {isbn}.");
}
return Results.Ok(llibre);
});
app.MapPost("/prestamos", async (SollicitudPrestec sollicitud, BibliotecaDbContext context) =>
{
Llibre? llibre = await context.Llibres.FirstOrDefaultAsync(l => l.Isbn == sollicitud.Isbn);
Soci? soci = await context.Socis.FindAsync(sollicitud.IdSoci);
if (llibre is null || soci is null)
{
return Results.BadRequest("ISBN o soci no trobats.");
}
if (!llibre.Disponible)
{
return Results.BadRequest($"'{llibre.Titol}' ja esta prestat.");
}
llibre.Prestar(); // logica de domini ja existent, Modul 2
Prestec prestec = new Prestec(llibre, soci);
context.Prestecs.Add(prestec);
await context.SaveChangesAsync(); // EF Core, Modul 5
return Results.Created($"/prestamos/{prestec.Id}", prestec);
});
app.Run();// SollicitudPrestec.cs: un DTO (Data Transfer Object) senzill per al cos del POST
record SollicitudPrestec(string Isbn, int IdSoci);Cap endpoint reimplementa la lògica de domini: GET /libros i GET /libros/{isbn} només
consulten BibliotecaDbContext (ja construït al Mòdul 5), i POST /prestamos crida
llibre.Prestar() exactament igual que ho faria la consola o el formulari de Windows Forms.
L'API és, de nou, una capa d'accés nova sobre el mateix domini, ara accessible per HTTP en lloc de
per teclat o clic de ratolí. SollicitudPrestec és un record (Mòdul 3) que modela el cos JSON
esperat al POST; ASP.NET Core el deserialitza automàticament des del cos de la petició, sense
codi addicional, aplicant el mateix mecanisme de binding de l'apartat 4.
- Provar l'API
Amb el projecte en execució (dotnet run), ASP.NET Core exposa típicament una interfície de
prova interactiva (Swagger/OpenAPI) en una URL com https://localhost:5001/swagger, a més de
poder-se provar directament amb curl o amb HttpClient (Mòdul 5):
curl https://localhost:5001/libros
curl -X POST https://localhost:5001/prestamos \
-H "Content-Type: application/json" \
-d '{"isbn": "978-84-376-0495-4", "idSoci": 1}'-H "Content-Type: application/json" indica al servidor que el cos de la petició (-d) és JSON,
perquè ASP.NET Core el deserialitzi correctament al paràmetre SollicitudPrestec de l'endpoint
POST /prestamos.
Errors Comuns i Consells
- Registrar
BibliotecaDbContextambAddSingletonen lloc d'AddDbContext: unDbContextno està pensat per compartir-se com a instància única entre peticions concurrents;AddDbContext(vist aquí) gestiona correctament crear una instància nova per petició. - Reimplementar la comprovació de
Disponibledins de l'endpoint en lloc de delegar-la allibre.Prestar(): duplicaria lògica ja resolta al domini des del Mòdul 2, amb risc de desincronitzar-se amb el temps. - Confondre Minimal APIs amb "sense injecció de dependències": Minimal APIs fa servir el mateix contenidor de serveis que els Controllers; només canvia com es registren els endpoints, no com funciona la injecció de dependències per sota.
- Consell: per a una API petita com aquesta, Minimal APIs manté tot l'enrutament visible a
Program.cs; si el nombre d'endpoints creixés molt, convé agrupar-los en fitxers separats ambapp.MapGroup(...)per no acabar amb un únic fitxer enorme.
Exercicis
-
Afegeix un endpoint
GET /socios/{id}que retorni elSocicorresponent (ambResults.Ok) oResults.NotFoundsi no existeix cap soci amb aquestid. -
Afegeix un endpoint
POST /devolucionesque rebi unrecord SollicitudDevolucio(int IdPrestec), busqui elPresteccorresponent, cridiRegistrarDevolucio()(Mòdul 2) i desi els canvis ambSaveChangesAsync(). RetornaResults.NotFoundsi el préstec no existeix.
Solucions
app.MapGet("/socios/{id}", async (int id, BibliotecaDbContext context) =>
{
Soci? soci = await context.Socis.FindAsync(id);
if (soci is null)
{
return Results.NotFound($"No existeix cap soci amb id {id}.");
}
return Results.Ok(soci);
});
record SollicitudDevolucio(int IdPrestec);
app.MapPost("/devoluciones", async (SollicitudDevolucio sollicitud, BibliotecaDbContext context) =>
{
Prestec? prestec = await context.Prestecs.FindAsync(sollicitud.IdPrestec);
if (prestec is null)
{
return Results.NotFound($"No existeix cap prestec amb id {sollicitud.IdPrestec}.");
}
prestec.RegistrarDevolucio(); // Modul 2
await context.SaveChangesAsync();
return Results.Ok(prestec);
});
Conclusió
En aquesta lliçó BiblioTech ha deixat de ser una aplicació d'un únic usuari i un únic ordinador:
amb ASP.NET Core i Minimal APIs, la seva lògica de domini —sense cap canvi— queda accessible per
HTTP mitjançant endpoints GET/POST, amb injecció de dependències bàsica per obtenir
BibliotecaDbContext a cada petició i serialització JSON automàtica de les respostes.
La lliçó següent presenta Blazor, que retoma directament aquesta API (o, segons el model triat, es connecta al domini de forma encara més directa) per construir una interfície d'usuari web amb C# en lloc de JavaScript —tancant el cercle entre el data binding ja vist a WPF i el món del navegador.
Curs de Programació en C#
Mòdul 1: Introducció al C#
- Introducció al C#
- Configuració de l'Entorn de Desenvolupament
- Programa Hola Món
- Sintaxi i Estructura Bàsica
- Variables i Tipus de Dades
- Arrays i Cadenes de Text
Mòdul 2: Estructures de Control
Mòdul 3: Programació Orientada a Objectes
- Classes i Objectes
- Mètodes
- Constructors i Destructors
- Herència
- Polimorfisme
- Encapsulació
- Abstracció
- Structs i Records: Tipus per Valor i per Referència
Mòdul 4: Conceptes Avançats de C#
- Interfícies
- Delegats i Esdeveniments
- Pattern Matching i Característiques Modernes de C#
- Genèrics
- Col·leccions
- LINQ (Consulta Integrada en el Llenguatge)
- Programació Asíncrona
Mòdul 5: Treballant amb Dades
- Entrada/Sortida de Fitxers
- Serialització
- Connectivitat amb Bases de Dades
- Entity Framework
- Treball amb JSON i Consum d'APIs REST
Mòdul 6: Temes Avançats
- Reflexió
- Atributs
- Programació Dinàmica
- Gestió de Memòria i Recollida d'Escombraries
- Multifil i Programació Paral·lela
Mòdul 7: Construcció d'Aplicacions
Mòdul 8: Bones Pràctiques i Patrons de Disseny
- Estàndards de Codificació i Bones Pràctiques
- Patrons de Disseny
- Injecció de Dependències i Inversió de Control
- Proves Unitàries
- Revisió i Refactorització de Codi
